Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-11 13:35:31 +03:00
parent 19fb8f2bcd
commit ecdb72e55c
58 changed files with 1082 additions and 346 deletions

View file

@ -1,5 +0,0 @@
package com.tangem.domain.visa.model
data class ActivationOrder(
val hash: String,
)

View file

@ -1,6 +0,0 @@
package com.tangem.domain.visa.model
data class SignedActivationOrder(
val activationOrder: ActivationOrder,
val signature: String,
)

View file

@ -4,6 +4,7 @@ enum class VisaActivationError(val message: String) {
BlockedForActivation("Card is blocked for activation"),
InvalidActivationState("Invalid activation state"),
WrongCard("Wrong card tapped"),
WrongRemoteState("Wrong remote state"),
MissingWallet("Missing wallet"),
MissingRootOTP("Missing root OTP"),
}

View file

@ -8,26 +8,6 @@ import kotlinx.serialization.Serializable
@JsonClass(generateAdapter = true)
data class VisaActivationInput(
@Json(name = "cardId") val cardId: String,
@Json(name = "cardPublicKey") val cardPublicKey: ByteArray,
@Json(name = "cardPublicKey") val cardPublicKey: String,
@Json(name = "isAccessCodeSet") val isAccessCodeSet: Boolean,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as VisaActivationInput
if (cardId != other.cardId) return false
if (!cardPublicKey.contentEquals(other.cardPublicKey)) return false
if (isAccessCodeSet != other.isAccessCodeSet) return false
return true
}
override fun hashCode(): Int {
var result = cardId.hashCode()
result = 31 * result + cardPublicKey.contentHashCode()
result = 31 * result + isAccessCodeSet.hashCode()
return result
}
}
)

View file

@ -1,9 +1,51 @@
package com.tangem.domain.visa.model
import com.squareup.moshi.*
import kotlinx.serialization.Serializable
@Serializable
enum class VisaActivationRemoteState {
sealed class VisaActivationRemoteState {
@Serializable
data class CardWalletSignatureRequired(
val request: VisaCardWalletDataToSignRequest,
) : VisaActivationRemoteState()
@Serializable
data class CustomerWalletSignatureRequired(
val request: VisaCardWalletDataToSignRequest,
) : VisaActivationRemoteState()
@Serializable
data object PaymentAccountDeploying : VisaActivationRemoteState()
@Serializable
data object WaitingPinCode : VisaActivationRemoteState()
@Serializable
data object WaitingForActivationFinishing : VisaActivationRemoteState()
@Serializable
data object Activated : VisaActivationRemoteState()
@Serializable
data object BlockedForActivation : VisaActivationRemoteState()
companion object {
val jsonAdapter: VisaActivationRemoteState_JsonAdapter = VisaActivationRemoteState_JsonAdapter()
}
}
@Suppress("ClassNaming")
class VisaActivationRemoteState_Json(
@Json(name = "type") val type: VisaActivationRemoteState_Type,
@Json(name = "request") val request: VisaCardWalletDataToSignRequest? = null,
)
@Suppress("ClassNaming")
// do not rename
@JsonClass(generateAdapter = false)
enum class VisaActivationRemoteState_Type {
CardWalletSignatureRequired,
CustomerWalletSignatureRequired,
PaymentAccountDeploying,
@ -11,4 +53,51 @@ enum class VisaActivationRemoteState {
WaitingForActivationFinishing,
Activated,
BlockedForActivation,
}
@Suppress("ClassNaming")
class VisaActivationRemoteState_JsonAdapter {
@FromJson
fun fromJson(value: VisaActivationRemoteState_Json): VisaActivationRemoteState {
return when (value.type) {
VisaActivationRemoteState_Type.CardWalletSignatureRequired ->
VisaActivationRemoteState.CardWalletSignatureRequired(value.request!!)
VisaActivationRemoteState_Type.CustomerWalletSignatureRequired ->
VisaActivationRemoteState.CustomerWalletSignatureRequired(value.request!!)
VisaActivationRemoteState_Type.PaymentAccountDeploying -> VisaActivationRemoteState.PaymentAccountDeploying
VisaActivationRemoteState_Type.WaitingPinCode -> VisaActivationRemoteState.WaitingPinCode
VisaActivationRemoteState_Type.WaitingForActivationFinishing ->
VisaActivationRemoteState.WaitingForActivationFinishing
VisaActivationRemoteState_Type.Activated -> VisaActivationRemoteState.Activated
VisaActivationRemoteState_Type.BlockedForActivation -> VisaActivationRemoteState.BlockedForActivation
}
}
@ToJson
fun toJson(value: VisaActivationRemoteState): VisaActivationRemoteState_Json {
return when (value) {
is VisaActivationRemoteState.CardWalletSignatureRequired ->
VisaActivationRemoteState_Json(
type = VisaActivationRemoteState_Type.CardWalletSignatureRequired,
request = value.request,
)
is VisaActivationRemoteState.CustomerWalletSignatureRequired ->
VisaActivationRemoteState_Json(
type = VisaActivationRemoteState_Type.CustomerWalletSignatureRequired,
request = value.request,
)
is VisaActivationRemoteState.PaymentAccountDeploying ->
VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.PaymentAccountDeploying)
is VisaActivationRemoteState.WaitingPinCode ->
VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.WaitingPinCode)
is VisaActivationRemoteState.WaitingForActivationFinishing ->
VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.WaitingForActivationFinishing)
is VisaActivationRemoteState.Activated -> VisaActivationRemoteState_Json(
VisaActivationRemoteState_Type.Activated,
)
is VisaActivationRemoteState.BlockedForActivation ->
VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.BlockedForActivation)
}
}
}

View file

@ -14,4 +14,8 @@ data class VisaAuthTokens(
@Serializable
@JvmInline
value class RefreshToken(val value: String)
}
fun VisaAuthTokens.getAuthHeader(): String {
return "Bearer $accessToken"
}

View file

@ -1,7 +1,9 @@
package com.tangem.domain.visa.model
import com.squareup.moshi.FromJson
import com.squareup.moshi.Json
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import com.squareup.moshi.JsonClass
import com.squareup.moshi.ToJson
import kotlinx.serialization.Serializable
@Serializable
@ -9,19 +11,19 @@ sealed class VisaCardActivationStatus {
@Serializable
data class Activated(
@Json(name = "visaAuthTokens") val visaAuthTokens: VisaAuthTokens,
val visaAuthTokens: VisaAuthTokens,
) : VisaCardActivationStatus()
@Serializable
data class ActivationStarted(
@Json(name = "activationInput") val activationInput: VisaActivationInput,
@Json(name = "authTokens") val authTokens: VisaAuthTokens,
@Json(name = "remoteState") val remoteState: VisaActivationRemoteState,
val activationInput: VisaActivationInput,
val authTokens: VisaAuthTokens,
val remoteState: VisaActivationRemoteState,
) : VisaCardActivationStatus()
@Serializable
data class NotStartedActivation(
@Json(name = "activationInput") val activationInput: VisaActivationInput,
val activationInput: VisaActivationInput,
) : VisaCardActivationStatus()
@Serializable
@ -31,11 +33,70 @@ sealed class VisaCardActivationStatus {
data object RefreshTokenExpired : VisaCardActivationStatus()
companion object {
val jsonAdapter: PolymorphicJsonAdapterFactory<VisaCardActivationStatus>
get() = PolymorphicJsonAdapterFactory.of(VisaCardActivationStatus::class.java, "type")
.withSubtype(Activated::class.java, "Activated")
.withSubtype(ActivationStarted::class.java, "ActivationStarted")
.withSubtype(NotStartedActivation::class.java, "NotStartedActivation")
.withDefaultValue(Blocked)
val jsonAdapter: VisaCardActivationStatus_JsonAdapter = VisaCardActivationStatus_JsonAdapter()
}
}
@Suppress("ClassNaming")
class VisaCardActivationStatus_Json(
@Json(name = "type") val type: VisaCardActivationStatus_Type,
@Json(name = "activationInput") val activationInput: VisaActivationInput? = null,
@Json(name = "authTokens") val authTokens: VisaAuthTokens? = null,
@Json(name = "remoteState") val remoteState: VisaActivationRemoteState? = null,
)
// do not rename instances
@Suppress("ClassNaming")
@JsonClass(generateAdapter = false)
enum class VisaCardActivationStatus_Type {
Activated,
ActivationStarted,
NotStartedActivation,
Blocked,
RefreshTokenExpired,
}
@Suppress("ClassNaming")
class VisaCardActivationStatus_JsonAdapter {
@FromJson
fun fromJson(value: VisaCardActivationStatus_Json): VisaCardActivationStatus {
return when (value.type) {
VisaCardActivationStatus_Type.Activated -> VisaCardActivationStatus.Activated(value.authTokens!!)
VisaCardActivationStatus_Type.ActivationStarted -> VisaCardActivationStatus.ActivationStarted(
value.activationInput!!,
value.authTokens!!,
value.remoteState!!,
)
VisaCardActivationStatus_Type.NotStartedActivation -> VisaCardActivationStatus.NotStartedActivation(
value.activationInput!!,
)
VisaCardActivationStatus_Type.Blocked -> VisaCardActivationStatus.Blocked
VisaCardActivationStatus_Type.RefreshTokenExpired -> VisaCardActivationStatus.RefreshTokenExpired
}
}
@ToJson
fun toJson(value: VisaCardActivationStatus): VisaCardActivationStatus_Json {
return when (value) {
is VisaCardActivationStatus.Activated -> VisaCardActivationStatus_Json(
VisaCardActivationStatus_Type.Activated,
authTokens = value.visaAuthTokens,
)
is VisaCardActivationStatus.ActivationStarted -> VisaCardActivationStatus_Json(
VisaCardActivationStatus_Type.ActivationStarted,
value.activationInput,
value.authTokens,
value.remoteState,
)
is VisaCardActivationStatus.NotStartedActivation -> VisaCardActivationStatus_Json(
VisaCardActivationStatus_Type.NotStartedActivation,
activationInput = value.activationInput,
)
is VisaCardActivationStatus.Blocked -> VisaCardActivationStatus_Json(VisaCardActivationStatus_Type.Blocked)
is VisaCardActivationStatus.RefreshTokenExpired -> VisaCardActivationStatus_Json(
VisaCardActivationStatus_Type.RefreshTokenExpired,
)
}
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.visa.model
data class VisaCardId(
val cardId: String,
val cardPublicKey: String,
)

View file

@ -0,0 +1,13 @@
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 VisaCardWalletDataToSignRequest(
@Json(name = "orderId") val orderId: String,
@Json(name = "customer_id") val customerId: String,
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
)

View file

@ -0,0 +1,12 @@
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 VisaCustomerWalletDataToSignRequest(
@Json(name = "orderId") val orderId: String,
@Json(name = "card_wallet_address") val cardWalletAddress: String,
)

View file

@ -1,11 +1,10 @@
package com.tangem.domain.visa.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class VisaDataForApprove(
@SerialName("customerWalletCardId") val customerWalletCardId: String?,
@SerialName("targetAddress") val targetAddress: String,
@SerialName("approveHash") val approveHash: String,
val customerWalletCardId: String?,
val targetAddress: String,
val dataToSign: VisaDataToSignByCustomerWallet,
)

View file

@ -0,0 +1,15 @@
package com.tangem.domain.visa.model
data class VisaDataToSignByCardWallet(
val request: VisaCardWalletDataToSignRequest,
val hashToSign: String,
)
fun VisaDataToSignByCardWallet.sign(cardWalletAddress: String, rootOTP: String, otpCounter: Int, signature: String) =
VisaSignedActivationDataByCardWallet(
dataToSign = this,
cardWalletAddress = cardWalletAddress,
rootOTP = rootOTP,
otpCounter = otpCounter,
signature = signature,
)

View file

@ -0,0 +1,16 @@
package com.tangem.domain.visa.model
import kotlinx.serialization.Serializable
@Serializable
data class VisaDataToSignByCustomerWallet(
val request: VisaCustomerWalletDataToSignRequest,
val hashToSign: String,
)
fun VisaDataToSignByCustomerWallet.sign(signature: String, customerWalletAddress: String) =
VisaSignedDataByCustomerWallet(
dataToSign = this,
customerWalletAddress = customerWalletAddress,
signature = signature,
)

View file

@ -1,4 +0,0 @@
package com.tangem.domain.visa.model
@JvmInline
value class VisaRootOTP(val value: String)

View file

@ -0,0 +1,9 @@
package com.tangem.domain.visa.model
class VisaSignedActivationDataByCardWallet(
val dataToSign: VisaDataToSignByCardWallet,
val cardWalletAddress: String,
val rootOTP: String,
val otpCounter: Int,
val signature: String,
)

View file

@ -0,0 +1,7 @@
package com.tangem.domain.visa.model
data class VisaSignedDataByCustomerWallet(
val dataToSign: VisaDataToSignByCustomerWallet,
val customerWalletAddress: String,
val signature: String,
)

View file

@ -1,7 +1,6 @@
package com.tangem.domain.visa.repository
import com.tangem.domain.visa.model.ActivationOrder
import com.tangem.domain.visa.model.VisaActivationRemoteState
import com.tangem.domain.visa.model.*
interface VisaActivationRepository {
@ -9,9 +8,17 @@ interface VisaActivationRepository {
suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState
suspend fun getActivationOrderToSign(): ActivationOrder
suspend fun getCardWalletAcceptanceData(request: VisaCardWalletDataToSignRequest): VisaDataToSignByCardWallet
suspend fun getCustomerWalletAcceptanceData(
request: VisaCustomerWalletDataToSignRequest,
): VisaDataToSignByCustomerWallet
suspend fun activateCard(signedData: VisaSignedActivationDataByCardWallet)
suspend fun approveByCustomerWallet(signedData: VisaSignedDataByCustomerWallet)
interface Factory {
fun create(cardId: String): VisaActivationRepository
fun create(cardId: VisaCardId): VisaActivationRepository
}
}

View file

@ -8,7 +8,7 @@ interface VisaAuthRepository {
suspend fun getCardAuthChallenge(cardId: String, cardPublicKey: String): VisaAuthChallenge.Card
suspend fun getCustomerWalletAuthChallenge(cardId: String, walletPublicKey: String): VisaAuthChallenge.Wallet
suspend fun getCardWalletAuthChallenge(cardWalletAddress: String): VisaAuthChallenge.Wallet
suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): VisaAuthTokens