diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 6a80a26d24..a94cf067f5 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -13,8 +13,10 @@ import com.tangem.TangemSdk import com.tangem.domain.common.FeatureCoroutineExceptionHandler import com.tangem.operations.backup.BackupService import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager import com.tangem.tap.common.IntentHandler +import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.common.redux.global.AndroidResources @@ -47,13 +49,15 @@ private val mainCoroutineContext: CoroutineContext get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope") val mainScope = CoroutineScope(mainCoroutineContext) -class MainActivity : AppCompatActivity(), SnackbarHandler { +class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { private var snackbar: Snackbar? = null private val dialogManager = DialogManager() private val intentHandler = IntentHandler() private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) + private val onActivityResultCallbacks = mutableListOf() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -67,18 +71,17 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { store.dispatch(GlobalAction.SetResources(getAndroidResources())) store.dispatch( ShopAction.CheckIfGooglePayAvailable( - GooglePayService(createPaymentsClient(this), this) - ) + GooglePayService(createPaymentsClient(this), this), + ), ) } - private fun getAndroidResources(): AndroidResources { return AndroidResources( AndroidResources.RString( R.string.copy_toast_msg, - R.string.details_notification_erase_wallet_not_possible - ) + R.string.details_notification_erase_wallet_not_possible, + ), ) } @@ -92,7 +95,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { supportFragmentManager.registerFragmentLifecycleCallbacks( NavBarInsetsFragmentLifecycleCallback(), - true + true, ) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT @@ -137,7 +140,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { if (snackbar != null) return snackbar = Snackbar.make( - binding.fragmentContainer, getString(text), Snackbar.LENGTH_INDEFINITE + binding.fragmentContainer, getString(text), Snackbar.LENGTH_INDEFINITE, ) if (buttonTitle != null && action != null) { snackbar?.setAction(getString(buttonTitle), action) @@ -152,12 +155,23 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) + + onActivityResultCallbacks.forEach { it(requestCode, resultCode, data) } when (requestCode) { LOAD_PAYMENT_DATA_REQUEST_CODE -> { store.dispatch( - ShopAction.BuyWithGooglePay.HandleGooglePayResponse(resultCode, data) + ShopAction.BuyWithGooglePay.HandleGooglePayResponse(resultCode, data), ) } } } + + override fun addOnActivityResultCallback(callback: OnActivityResultCallback) { + onActivityResultCallbacks.remove(callback) + onActivityResultCallbacks.add(callback) + } + + override fun removeOnActivityResultCallback(callback: OnActivityResultCallback) { + onActivityResultCallbacks.remove(callback) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/ActivityResultCallbackHolder.kt b/app/src/main/java/com/tangem/tap/common/ActivityResultCallbackHolder.kt new file mode 100644 index 0000000000..931922493c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/ActivityResultCallbackHolder.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.common + +import android.content.Intent + +/** +[REDACTED_AUTHOR] + */ +interface ActivityResultCallbackHolder { + fun addOnActivityResultCallback(callback: OnActivityResultCallback) + fun removeOnActivityResultCallback(callback: OnActivityResultCallback) +} + +typealias OnActivityResultCallback = (Int, Int, Intent?) -> Unit \ No newline at end of file diff --git a/common/src/main/java/com/tangem/common/Filter.kt b/common/src/main/java/com/tangem/common/Filter.kt new file mode 100644 index 0000000000..6607f4080f --- /dev/null +++ b/common/src/main/java/com/tangem/common/Filter.kt @@ -0,0 +1,8 @@ +package com.tangem.common + +/** +[REDACTED_AUTHOR] + */ +interface Filter { + fun filter(value: T): Boolean +} \ No newline at end of file diff --git a/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt b/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt new file mode 100644 index 0000000000..11da28c037 --- /dev/null +++ b/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt @@ -0,0 +1,13 @@ +package com.tangem.common.module + +/** +[REDACTED_AUTHOR] + */ +object ModuleErrorCode { + const val APP = 100000 + + const val COMMON = 10000 + const val NETWORK = 20000 + const val DOMAIN = 30000 + const val SALT_PAY = 40000 +} \ No newline at end of file diff --git a/common/src/main/java/com/tangem/common/module/ModuleException.kt b/common/src/main/java/com/tangem/common/module/ModuleException.kt deleted file mode 100644 index 233d47f942..0000000000 --- a/common/src/main/java/com/tangem/common/module/ModuleException.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.common.module - -/** -[REDACTED_AUTHOR] - * A module exception - */ -interface ModuleException { - val message: String -} - -/** - * An exception marked as FbConsumeException should be submitted to Firebase.Crashlytics as a non-fatal issue. - */ -interface FbConsumeException \ No newline at end of file diff --git a/common/src/main/java/com/tangem/common/module/ModuleMessage.kt b/common/src/main/java/com/tangem/common/module/ModuleMessage.kt index 240e7c8759..c8632c9597 100644 --- a/common/src/main/java/com/tangem/common/module/ModuleMessage.kt +++ b/common/src/main/java/com/tangem/common/module/ModuleMessage.kt @@ -6,17 +6,22 @@ package com.tangem.common.module */ interface ModuleMessage -interface ModuleMessageConverter { - fun convert(message: ModuleMessage): R -} - /** * @property code describes what feature is the error coming from * @property message the error description * @property data any data that can help in the part where this error is being handled */ -interface ModuleError : ModuleMessage { - val code: Int - val message: String - val data: Any? +abstract class ModuleError() : Throwable(), ModuleMessage { + abstract val code: Int + abstract override val message: String + abstract val data: Any? +} + +/** + * An exception marked as FbConsumeException should be submitted to Firebase.Crashlytics as a non-fatal issue. + */ +interface FbConsumeException + +interface ModuleMessageConverter { + fun convert(message: ModuleMessage): R } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/DomainDialog.kt b/domain/src/main/java/com/tangem/domain/DomainDialog.kt index 3e956b77da..bc7d30a7da 100644 --- a/domain/src/main/java/com/tangem/domain/DomainDialog.kt +++ b/domain/src/main/java/com/tangem/domain/DomainDialog.kt @@ -8,7 +8,7 @@ import com.tangem.network.api.tangemTech.CoinsResponse */ sealed interface DomainDialog { - data class DialogError(val error: DomainError) : DomainDialog + data class DialogError(val error: DomainModuleError) : DomainDialog data class SelectTokenDialog( val items: List, diff --git a/domain/src/main/java/com/tangem/domain/DomainError.kt b/domain/src/main/java/com/tangem/domain/DomainError.kt deleted file mode 100644 index 74ff7f01a4..0000000000 --- a/domain/src/main/java/com/tangem/domain/DomainError.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.tangem.domain - -import com.tangem.common.module.ModuleError -import com.tangem.common.module.ModuleMessage - -/** -[REDACTED_AUTHOR] - * All DomainError descendants must use their own range of codes, but no more than 999 error codes for each. - */ -sealed interface DomainModuleMessage : ModuleMessage - -sealed class DomainError( - subCode: Int, - override val message: String, - override val data: Any?, -) : DomainModuleMessage, ModuleError { - override val code: Int = ERROR_CODE_DOMAIN + subCode - - companion object { - // base code used for all error in the module - const val ERROR_CODE_DOMAIN = 10000 - const val ERROR_CODE_ADD_CUSTOM_TOKEN = 100 -// const val CODE_ANY_OTHER = 200..299 - } -} - -sealed class AddCustomTokenError( - subCode: Int = 0 -) : DomainError(ERROR_CODE_ADD_CUSTOM_TOKEN + subCode, this::class.java.simpleName, null) { - - object FieldIsEmpty : AddCustomTokenError() - object FieldIsNotEmpty : AddCustomTokenError() - object InvalidContractAddress : AddCustomTokenError() - object NetworkIsNotSelected : AddCustomTokenError() - object InvalidDecimalsCount : AddCustomTokenError() - object InvalidDerivationPath : AddCustomTokenError() - - sealed class Network : AddCustomTokenError() { - object CheckAddressRequestError : Network() - } - - sealed class Warning : AddCustomTokenError() { - object PotentialScamToken : Warning() - object TokenAlreadyAdded : Warning() - object UnsupportedSolanaToken: Warning() - } -} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/DomainException.kt b/domain/src/main/java/com/tangem/domain/DomainException.kt deleted file mode 100644 index a2661db781..0000000000 --- a/domain/src/main/java/com/tangem/domain/DomainException.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.domain - -import com.tangem.common.module.FbConsumeException -import com.tangem.common.module.ModuleException - -/** -[REDACTED_AUTHOR] - */ -sealed class DomainException(override val message: String) : Throwable(message), ModuleException - -sealed class AddCustomTokenException(message: String) : DomainException(message) { - - data class SelectTokeNetworkException(val networkId: String) : AddCustomTokenException( - "Unknown network [$networkId] should not be included in the network selection dialog." - ), FbConsumeException - - data class UnAppropriateInitializationException( - val of: String, - val info: String? = null - ) : AddCustomTokenException("The [$of], must be properly initialized. Info [$info]") -} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/DomainLayer.kt b/domain/src/main/java/com/tangem/domain/DomainLayer.kt index f83049dbf8..ef083fdc96 100644 --- a/domain/src/main/java/com/tangem/domain/DomainLayer.kt +++ b/domain/src/main/java/com/tangem/domain/DomainLayer.kt @@ -10,7 +10,7 @@ import com.tangem.domain.redux.state.ActionStateLoggerImpl object DomainLayer { internal val actionStateLogger = ActionStateLoggerImpl() - var onInitComplete: ((DomainException?) -> Unit)? = null + var onInitComplete: ((DomainModuleError?) -> Unit)? = null fun init(data: Map? = null) { initActionStateLogger() diff --git a/domain/src/main/java/com/tangem/domain/DomainModuleMessage.kt b/domain/src/main/java/com/tangem/domain/DomainModuleMessage.kt new file mode 100644 index 0000000000..6eb55484ca --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/DomainModuleMessage.kt @@ -0,0 +1,65 @@ +package com.tangem.domain + +import com.tangem.common.module.FbConsumeException +import com.tangem.common.module.ModuleError +import com.tangem.common.module.ModuleErrorCode +import com.tangem.common.module.ModuleMessage + +/** +[REDACTED_AUTHOR] + * All DomainError descendants must use their own range of codes, but no more than 999 error codes for each. + */ +sealed interface DomainModuleMessage : ModuleMessage + +sealed class DomainModuleError( + subCode: Int, + override val message: String, + override val data: Any?, +) : DomainModuleMessage, ModuleError() { + override val code: Int = ModuleErrorCode.DOMAIN + subCode + + companion object { + // base code used for all errors in the module + internal const val ERROR_CODE_ADD_CUSTOM_TOKEN = 100 +// const val CODE_ANY_OTHER = 200..299, 300..399 etc + } +} + +sealed class AddCustomTokenError( + subCode: Int = 0, + message: String? = null, + data: Any? = null, +) : DomainModuleError( + subCode = ERROR_CODE_ADD_CUSTOM_TOKEN + subCode, + message = message ?: this::class.java.simpleName, + data = data, +) { + + object FieldIsEmpty : AddCustomTokenError() + object FieldIsNotEmpty : AddCustomTokenError() + object InvalidContractAddress : AddCustomTokenError() + object NetworkIsNotSelected : AddCustomTokenError() + object InvalidDecimalsCount : AddCustomTokenError() + object InvalidDerivationPath : AddCustomTokenError() + + sealed class Network : AddCustomTokenError() { + object CheckAddressRequestError : Network() + } + + sealed class Warning : AddCustomTokenError() { + object PotentialScamToken : Warning() + object TokenAlreadyAdded : Warning() + object UnsupportedSolanaToken : Warning() + } + + data class SelectTokeNetworkError(val networkId: String) : AddCustomTokenError( + message = "Unknown network [$networkId] should not be included in the network selection dialog.", + ), FbConsumeException + + data class UnAppropriateInitialization( + val of: String, + val info: String? = null, + ) : AddCustomTokenError( + message = "The [$of], must be properly initialized. Info [$info]", + ) +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt new file mode 100644 index 0000000000..839d59bd76 --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -0,0 +1,93 @@ +package com.tangem.domain.common + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import com.tangem.common.CardIdRange +import com.tangem.common.contains +import com.tangem.domain.features.BuildConfig + +object SaltPayWorkaround { + fun tokenFrom(blockchain: Blockchain): Token { + return when (blockchain) { + Blockchain.SaltPay -> Token( + name = "WXDAI", + symbol = "WXDAI", + contractAddress = "0x4346186e7461cB4DF06bCFCB4cD591423022e417", + decimals = 18, + id = "xdai", + ) + Blockchain.SaltPayTestnet -> Token( + name = "WXDAI Test", + symbol = "MyERC20", + contractAddress = "0x69cca8D8295de046C7c14019D9029Ccc77987A48", + decimals = 0, + id = "xdai", + ) + else -> throw IllegalArgumentException() + } + } + + val visaBatches = listOf( + "AE02", + "AE03", + ) + attachDebugVisaBatches() + + val tangemWalletCardIds = listOf( + "AC01000000033503", + "AC01000000033594", + "AC01000000033586", + "AC01000000034477", + "AC01000000032760", + "AC01000000033867", + "AC01000000032653", + "AC01000000032752", + "AC01000000034485", + "AC01000000033644", + "AC01000000037454", + "AC01000000037462", + "AC03000000076070", + "AC03000000076088", + "AC03000000076096", + "AC03000000076104", + "AC03000000076112", + "AC03000000076120", + "AC03000000076138", + "AC03000000076146", + "AC03000000076153", + "AC03000000076161", + "AC03000000076179", + "AC03000000076187", + "AC03000000076195", + "AC03000000076203", + "AC03000000076211", + "AC03000000076229", + ) + attachDebugTangemCardIds() + + val tangemWalletCardIdRanges = listOf( + CardIdRange("AC05000000000003", "AC05000000023997")!!, + ) + attachDebugTangemCardIdRanges() + + fun isVisaBatchId(batchId: String): Boolean = visaBatches.contains(batchId) + + fun isTangemWalletCardId(cardId: String): Boolean { + return if (tangemWalletCardIds.contains(cardId)) true else tangemWalletCardIdRanges.contains(cardId) + } + + private fun attachDebugVisaBatches(): List { + return if (BuildConfig.DEBUG) listOf( + "FF03", + ) else listOf() + } + + private fun attachDebugTangemCardIds(): List { + return if (BuildConfig.DEBUG) listOf( + "FF04000000000232", + ) else listOf() + } + + private fun attachDebugTangemCardIdRanges(): List { + return if (BuildConfig.DEBUG) listOf( + CardIdRange("FF04000000000000", "FF04999999999999")!!, + ) else listOf() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt index 35c6b31736..7fa3def1a8 100644 --- a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt +++ b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt @@ -8,10 +8,10 @@ import com.tangem.common.card.WalletData import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath -import com.tangem.domain.common.TapWorkarounds.getSaltPayBlockchain import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isTangemNote +import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.operations.CommandResponse import com.tangem.operations.backup.PrimaryCard @@ -30,12 +30,8 @@ data class ScanResponse( ) : CommandResponse { fun getBlockchain(): Blockchain { return when (productType) { - ProductType.SaltPay -> { - card.getSaltPayBlockchain() - } - ProductType.Note -> { - card.getTangemNoteBlockchain() ?: Blockchain.Unknown - } + ProductType.SaltPay -> if (card.isTestCard) Blockchain.SaltPayTestnet else Blockchain.SaltPay + ProductType.Note -> card.getTangemNoteBlockchain() ?: Blockchain.Unknown else -> { val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown Blockchain.fromId(blockchainName) @@ -44,7 +40,7 @@ data class ScanResponse( } fun getPrimaryToken(): Token? { - if (card.isSaltPay) return TapWorkarounds.saltPayToken + if (card.isSaltPay) return SaltPayWorkaround.tokenFrom(getBlockchain()) val cardToken = walletData?.token ?: return null return Token( @@ -57,11 +53,11 @@ data class ScanResponse( fun isTangemNote(): Boolean = productType == ProductType.Note fun isTangemWallet(): Boolean = productType == ProductType.Wallet + fun isSaltPay(): Boolean = productType == ProductType.SaltPay fun isTangemTwins(): Boolean = productType == ProductType.Twins + fun twinsIsTwinned(): Boolean = card.isTangemTwins && walletData != null && secondTwinPublicKey != null fun supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed fun supportsBackup(): Boolean = card.settings.isBackupAllowed - fun twinsIsTwinned(): Boolean = - card.isTangemTwins() && walletData != null && secondTwinPublicKey != null fun hasDerivation(blockchain: Blockchain, rawDerivationPath: String): Boolean { return hasDerivation(blockchain, DerivationPath(rawDerivationPath)) @@ -95,12 +91,10 @@ enum class ProductType { val Card.productType: ProductType get() = when { - isTangemTwins() -> ProductType.Twins - isTangemNote() -> ProductType.Note + isTangemTwins -> ProductType.Twins + isTangemNote -> ProductType.Note isSaltPay -> ProductType.SaltPay else -> ProductType.Wallet } -typealias KeyWalletPublicKey = ByteArrayKey - -fun Card.isTangemTwins(): Boolean = TwinsHelper.getTwinCardNumber(cardId) != null \ No newline at end of file +typealias KeyWalletPublicKey = ByteArrayKey \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index 6c85f64266..3ffc1bf352 100644 --- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -2,7 +2,6 @@ package com.tangem.domain.common import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle -import com.tangem.blockchain.common.Token import com.tangem.common.card.Card import java.util.* @@ -10,6 +9,10 @@ import java.util.* [REDACTED_AUTHOR] */ object TapWorkarounds { + private const val START_2_COIN_ISSUER = "start2coin" + private const val TEST_CARD_BATCH = "99FF" + private const val TEST_CARD_ID_STARTS_WITH = "FF99" + fun isStart2CoinIssuer(cardIssuer: String?): Boolean { return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER } @@ -17,21 +20,15 @@ object TapWorkarounds { val Card.isStart2Coin: Boolean get() = isStart2CoinIssuer(issuer.name) - val Card.isSaltPay: Boolean - get() = isSaltPayVisa || isSaltPayTangem - - val Card.isSaltPayVisa: Boolean - get() = saltPayVisaBatches.contains(batchId) - - val Card.isSaltPayTangem: Boolean - get() = saltPayTangemCardIds.contains(cardId) - val Card.isTestCard: Boolean get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH) + val Card.useOldStyleDerivation: Boolean get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95" + val Card.derivationStyle: DerivationStyle? get() = if (!settings.isHDWalletAllowed) { + Int.MAX_VALUE null } else if (useOldStyleDerivation) { DerivationStyle.LEGACY @@ -39,38 +36,15 @@ object TapWorkarounds { DerivationStyle.NEW } - fun Card.isExcluded(): Boolean { - val excludedBatch = excludedBatches.contains(batchId) - val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT)) - return excludedBatch || excludedIssuerName - } + val Card.isNotSupportedInThatRelease: Boolean + get() = false - fun Card.isNotSupportedInThatRelease(): Boolean { - return false - } + val Card.isTangemTwins: Boolean + get() = TwinsHelper.getTwinCardNumber(cardId) != null - fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) - fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId) - fun Card.getTangemNoteBlockchain(): Blockchain? = - tangemNoteBatches[batchId] ?: null - - fun Card.getSaltPayBlockchain(): Blockchain = Blockchain.SaltPay - - private const val START_2_COIN_ISSUER = "start2coin" - private const val TEST_CARD_BATCH = "99FF" - private const val TEST_CARD_ID_STARTS_WITH = "FF99" - private val excludedBatches = listOf( - "0027", - "0030", - "0031", - "0035", - ) - - private val excludedIssuers = listOf( - "TTM BANK", - ) - - private val tangemWalletBatches = listOf("AC01") + //TODO: replace by reading files from a card + val Card.isTangemNote: Boolean + get() = tangemNoteBatches.contains(batchId) private val tangemNoteBatches = mapOf( "AB01" to Blockchain.Bitcoin, @@ -87,55 +61,34 @@ object TapWorkarounds { "AB12" to Blockchain.Ethereum, ) - private val tangemWalletBatchesWithStandardDerivationType = listOf( - "AC01", "AC02", "CB95", + fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId] ?: null + + + val Card.isSaltPay: Boolean + get() = isSaltPayVisa || isSaltPayTangem + + val Card.isSaltPayVisa: Boolean + get() = SaltPayWorkaround.isVisaBatchId(batchId) + + val Card.isSaltPayTangem: Boolean + get() = SaltPayWorkaround.isTangemWalletCardId(cardId) + + + val Card.isExcluded: Boolean + get() { + val excludedBatch = excludedBatches.contains(batchId) + val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT)) + return excludedBatch || excludedIssuerName + } + + private val excludedBatches = listOf( + "0027", + "0030", + "0031", + "0035", ) - val saltPayTangemCardIds = listOf( - "AC01000000000015", // TODO: remove testing CIDs - "AC03000000000088", // TODO: remove testing CIDs - "AC03000000076070", - "AC03000000076088", - "AC03000000076096", - "AC03000000076104", - "AC03000000076112", - "AC03000000076120", - "AC03000000076138", - "AC03000000076146", - "AC03000000076153", - "AC03000000076161", - "AC03000000076179", - "AC03000000076187", - "AC03000000076195", - "AC03000000076203", - "AC03000000076211", - "AC03000000076229", - // added 01.10.2022 - "AC01000000033503", - "AC01000000033594", - "AC01000000033586", - "AC01000000034477", - "AC01000000032760", - "AC01000000033867", - "AC01000000032653", - "AC01000000032752", - "AC01000000034485", - "AC01000000033644", - "AC01000000037454", - "AC01000000037462", + private val excludedIssuers = listOf( + "TTM BANK", ) - - private val saltPayVisaBatches = listOf( - "AE02", - "AE03", - ) - - val saltPayToken: Token - get() = Token( - name = "Wrapped xDAI", - symbol = "WxDAI", - contractAddress = "0x4346186e7461cB4DF06bCFCB4cD591423022e417", - decimals = 18, - id = "xdai", - ) } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/card/CardIdRange.kt b/domain/src/main/java/com/tangem/domain/common/card/CardIdRange.kt deleted file mode 100644 index 696050068f..0000000000 --- a/domain/src/main/java/com/tangem/domain/common/card/CardIdRange.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.domain.common.card - -import timber.log.Timber - -/** -[REDACTED_AUTHOR] - */ -class CardIdRange( - val cardIdStart: String, - val cardIdEnd: String, -) { - val range: LongRange - - init { - checkCardId(cardIdStart) - checkCardId(cardIdEnd) - range = toLong(cardIdStart)..toLong(cardIdEnd) - } - - fun contains(cardId: String): Boolean = try { - checkCardId(cardId) - range.contains(toLong(cardId)) - } catch (ex: Exception) { - Timber.e(ex, "CardIdRange: check for the cardId: [$cardId] in range failed.") - false - } - - private fun stripBatchPrefix(cardId: String): String = cardId.drop(4) - - @Throws(NumberFormatException::class) - private fun toLong(cardId: String): Long = stripBatchPrefix(cardId).toLong() - - private fun checkCardId(cardId: String) { - if (cardId.length != 16) throw IllegalArgumentException("CardId must be 16 characters long.") - } -} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 35ada82c64..61d47d0034 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -48,6 +48,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "optimistic-ethereum/test" -> Blockchain.OptimismTestnet "dash" -> Blockchain.Dash "wxdai" -> Blockchain.SaltPay + "wxdai/test" -> Blockchain.SaltPayTestnet else -> null } } @@ -100,6 +101,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.OptimismTestnet -> "optimistic-ethereum/test" Blockchain.Dash -> "dash" Blockchain.SaltPay -> "wxdai" + Blockchain.SaltPayTestnet -> "wxdai/test" } } @@ -131,7 +133,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Kusama -> "kusama" Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum" Blockchain.Dash -> "dash" - Blockchain.SaltPay -> "xdai" + Blockchain.SaltPay, Blockchain.SaltPayTestnet -> "xdai" Blockchain.Unknown -> "unknown" } } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/ResultCardSdk.kt b/domain/src/main/java/com/tangem/domain/common/extensions/ResultCardSdk.kt new file mode 100644 index 0000000000..d3d0f8e112 --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/extensions/ResultCardSdk.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.common.extensions + +import com.tangem.common.CompletionResult +import com.tangem.common.services.Result + +/** +[REDACTED_AUTHOR] + */ +inline fun Result.successOr(failureClause: (Result.Failure) -> T): T { + return when (this) { + is Result.Success -> this.data + is Result.Failure -> failureClause(this) + } +} + +inline fun CompletionResult.successOr(failureClause: (CompletionResult.Failure) -> T): T { + return when (this) { + is CompletionResult.Success -> this.data + is CompletionResult.Failure -> failureClause(this) + } +} + diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index dfc524e857..2b56a47f90 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -8,7 +8,6 @@ import com.tangem.domain.AddCustomTokenError import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded import com.tangem.domain.AddCustomTokenError.Warning.UnsupportedSolanaToken -import com.tangem.domain.AddCustomTokenException import com.tangem.domain.DomainDialog import com.tangem.domain.DomainWrapped import com.tangem.domain.common.TapWorkarounds.derivationStyle @@ -200,7 +199,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT networkIdConverter = { networkId -> val blockchain = Blockchain.fromNetworkId(networkId) if (blockchain == null || blockchain == Blockchain.Unknown) { - throw AddCustomTokenException.SelectTokeNetworkException(networkId) + throw AddCustomTokenError.SelectTokeNetworkError(networkId) } hubState.blockchainToName(blockchain) ?: "" }, @@ -488,7 +487,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT @Throws private fun throwUnAppropriateInitialization(objName: String) { - throw AddCustomTokenException.UnAppropriateInitializationException( + throw AddCustomTokenError.UnAppropriateInitialization( "AddCustomTokenHub", "$objName must be not NULL" ) } diff --git a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt index 1f2109b371..b420816c55 100644 --- a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt +++ b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt @@ -3,6 +3,7 @@ package com.tangem.domain.redux.global import com.tangem.domain.DomainDialog import com.tangem.domain.common.LogConfig import com.tangem.domain.common.ScanResponse +import com.tangem.network.api.paymentology.PaymentologyApiService import com.tangem.network.api.tangemTech.TangemTechService /** @@ -18,5 +19,6 @@ data class DomainGlobalState( ) data class NetworkServices( - val tangemTechService: TangemTechService = TangemTechService(LogConfig.network.tangemTechService) + val tangemTechService: TangemTechService = TangemTechService(LogConfig.network.tangemTechService), + val paymentologyService: PaymentologyApiService = PaymentologyApiService(LogConfig.network.paymentologyApiService), ) diff --git a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt index cbefd39af8..058cd368dd 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt +++ b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt @@ -1,6 +1,7 @@ package com.tangem.network.api.paymentology import retrofit2.http.Body +import retrofit2.http.Headers import retrofit2.http.POST /** @@ -8,21 +9,30 @@ import retrofit2.http.POST */ interface PaymentologyApi { + @Headers("Content-Type: application/json") @POST("card/verify") - fun checkRegistration( + suspend fun checkRegistration( @Body request: CheckRegistrationRequests, - ): RegistrationResponse.Item + ): RegistrationResponse + @Headers("Content-Type: application/json") @POST("card/get_challenge") - fun requestAttestationChallenge( + suspend fun requestAttestationChallenge( @Body request: CheckRegistrationRequests.Item, ): AttestationResponse + @Headers("Content-Type: application/json") @POST("card/set_pin") - fun registerWallet( + suspend fun registerWallet( @Body request: RegisterWalletRequest, ): RegisterWalletResponse + @Headers("Content-Type: application/json") + @POST("card/kyc") + suspend fun registerKYC( + @Body request: RegisterKYCRequest, + ): RegisterWalletResponse + companion object { val baseUrl: String = "https://paymentologygate.oa.r.appspot.com/" } diff --git a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt index 0fce5b41ba..8cd70891c3 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt +++ b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt @@ -3,6 +3,7 @@ package com.tangem.network.api.paymentology import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.common.services.performRequest +import com.tangem.network.common.MoshiConverter import com.tangem.network.common.createRetrofitInstance import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -15,17 +16,19 @@ class PaymentologyApiService( ) { private val api = createRetrofitInstance( baseUrl = PaymentologyApi.baseUrl, + converterFactory = MoshiConverter.createFactory(MoshiConverter.sdkMoshi()), logEnabled = logEnabled, ).create(PaymentologyApi::class.java) suspend fun checkRegistration( cardId: String, publicKey: ByteArray, - ): Result = withContext(Dispatchers.IO) { - // later convert response to SaltPayRegistrator.State + ): Result = withContext(Dispatchers.IO) { val requestItem = CheckRegistrationRequests.Item(cardId, publicKey.toHexString()) - val requests = listOf(requestItem) - performRequest { api.checkRegistration(CheckRegistrationRequests(requests)) } + val request = CheckRegistrationRequests(listOf(requestItem)) + performRequest { + api.checkRegistration(request) + } } suspend fun requestAttestationChallenge( @@ -33,12 +36,28 @@ class PaymentologyApiService( publicKey: ByteArray, ): Result = withContext(Dispatchers.IO) { val requestItem = CheckRegistrationRequests.Item(cardId, publicKey.toHexString()) - performRequest { api.requestAttestationChallenge(requestItem) } + performRequest { + api.requestAttestationChallenge(requestItem) + } } suspend fun registerWallet( request: RegisterWalletRequest, ): Result = withContext(Dispatchers.IO) { - performRequest { api.registerWallet(request) } + performRequest { + api.registerWallet(request) + } + } + + suspend fun registerKYC( + request: RegisterKYCRequest, + ): Result = withContext(Dispatchers.IO) { + performRequest { + api.registerKYC(request) + } + } + + companion object { + fun stub(): PaymentologyApiService = PaymentologyApiService(false) } } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt b/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt index 1cea8aada4..580e60bfa5 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt +++ b/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt @@ -1,6 +1,7 @@ package com.tangem.network.api.paymentology import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import com.tangem.common.extensions.calculateHashCode /** @@ -9,10 +10,11 @@ import com.tangem.common.extensions.calculateHashCode data class CheckRegistrationRequests( val requests: List, ) { + @JsonClass(generateAdapter = true) data class Item( @Json(name = "CID") - var cardId: String = "", - var publicKey: String = "", + val cardId: String = "", + val publicKey: String = "", ) } @@ -35,12 +37,12 @@ data class RegisterWalletRequest( other as RegisterWalletRequest if (cardId != other.cardId) return false - if (publicKey.contentEquals(other.publicKey)) return false - if (walletPublicKey.contentEquals(other.walletPublicKey)) return false - if (walletSalt.contentEquals(other.walletSalt)) return false - if (walletSignature.contentEquals(other.walletSignature)) return false - if (cardSalt.contentEquals(other.cardSalt)) return false - if (cardSignature.contentEquals(other.cardSignature)) return false + if (!publicKey.contentEquals(other.publicKey)) return false + if (!walletPublicKey.contentEquals(other.walletPublicKey)) return false + if (!walletSalt.contentEquals(other.walletSalt)) return false + if (!walletSignature.contentEquals(other.walletSignature)) return false + if (!cardSalt.contentEquals(other.cardSalt)) return false + if (!cardSignature.contentEquals(other.cardSignature)) return false if (pin != other.pin) return false return true @@ -56,4 +58,34 @@ data class RegisterWalletRequest( cardSignature.contentHashCode(), pin.hashCode(), ) +} + +data class RegisterKYCRequest( + @Json(name = "CID") + val cardId: String, + val publicKey: ByteArray, + val kycProvider: String, + val kycRefId: String, +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RegisterKYCRequest + + if (cardId != other.cardId) return false + if (!publicKey.contentEquals(other.publicKey)) return false + if (kycProvider != other.kycProvider) return false + if (kycRefId != other.kycRefId) return false + + return true + } + + override fun hashCode(): Int = calculateHashCode( + cardId.hashCode(), + publicKey.contentHashCode(), + kycProvider.hashCode(), + kycRefId.hashCode(), + ) } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt index 7edd003b23..e70fdd3b1b 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt @@ -6,16 +6,12 @@ import com.tangem.common.extensions.calculateHashCode /** [REDACTED_AUTHOR] */ -interface ErrorContainer { - val error: String? -} - data class RegistrationResponse( - val results: List, + val results: List = listOf(), val success: Boolean, - override val error: String?, - val errorCode: String?, -) : ErrorContainer { + val error: String?, + val errorCode: Int?, +) { data class Item( @Json(name = "CID") @@ -28,20 +24,22 @@ data class RegistrationResponse( val blockchainInit: Boolean?, @Json(name = "kyc_passed") val kycPassed: Boolean?, - @Json(name = "kyc_waiting") - val kycWaiting: Boolean?, + @Json(name = "kyc_provider") + val kycProvider: String?, + @Json(name = "kyc_date") + val kycDate: String?, @Json(name = "disabled_by_admin") val disabledByAdmin: Boolean?, - override val error: String?, - ) : ErrorContainer + val error: String?, + ) } data class AttestationResponse( - val challenge: ByteArray, + val challenge: ByteArray?, val success: Boolean, - override val error: String?, - val errorCode: String?, -) : ErrorContainer { + val error: String?, + val errorCode: Int?, +) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -49,7 +47,7 @@ data class AttestationResponse( other as AttestationResponse - if (challenge.contentEquals(other.challenge)) return false + if (!challenge.contentEquals(other.challenge)) return false if (success != other.success) return false if (error != other.error) return false if (errorCode != other.errorCode) return false @@ -67,6 +65,6 @@ data class AttestationResponse( data class RegisterWalletResponse( val success: Boolean, - override val error: String?, - val errorCode: String?, -) : ErrorContainer \ No newline at end of file + val error: String?, + val errorCode: Int?, +) \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/NetworError.kt b/network/src/main/java/com/tangem/network/common/NetworError.kt index bdee8a8729..dfe852ecc9 100644 --- a/network/src/main/java/com/tangem/network/common/NetworError.kt +++ b/network/src/main/java/com/tangem/network/common/NetworError.kt @@ -1,6 +1,7 @@ package com.tangem.network.common import com.tangem.common.module.ModuleError +import com.tangem.common.module.ModuleErrorCode import com.tangem.common.module.ModuleMessage /** @@ -12,12 +13,11 @@ sealed class NetworkError( subCode: Int, override val message: String, override val data: Any?, -) : NetworkModuleMessage, ModuleError { - override val code: Int = ERROR_CODE_NETWORK + subCode +) : NetworkModuleMessage, ModuleError() { + override val code: Int = ModuleErrorCode.NETWORK + subCode companion object { // base code used for all error in the module - const val ERROR_CODE_NETWORK = 20000 - // const val CODE_ANY_OTHER = 100..199 + // const val CODE_ANY_OTHER = 100..199, 200..299 } } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/NetworkException.kt b/network/src/main/java/com/tangem/network/common/NetworkException.kt deleted file mode 100644 index 033e2b9b62..0000000000 --- a/network/src/main/java/com/tangem/network/common/NetworkException.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.network.common - -/** -[REDACTED_AUTHOR] - */ -interface NetworkInternalException - -sealed class NetworkException(message: String?) : Throwable(message), NetworkInternalException { -} \ No newline at end of file