Updated on 2026-08-14
This commit is contained in:
parent
36aaeca687
commit
f27a28f0b7
18 changed files with 357 additions and 303 deletions
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.data.visa
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.visa.config.VisaLibLoader
|
||||
import com.tangem.data.visa.converter.VisaActivationStatusConverterWithState
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
|
|
@ -8,14 +11,11 @@ import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
|||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.visa.TangemVisaApi
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest
|
||||
import com.tangem.datasource.api.visa.models.request.*
|
||||
import com.tangem.datasource.api.visa.models.response.VisaErrorResponseJsonAdapter
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||
|
|
@ -28,6 +28,7 @@ import kotlinx.coroutines.withContext
|
|||
@Suppress("LongParameterList")
|
||||
internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
||||
@Assisted private val visaCardId: VisaCardId,
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val visaApi: TangemVisaApi,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||
|
|
@ -36,27 +37,30 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
private val apiConfigsManager: ApiConfigsManager,
|
||||
) : VisaActivationRepository {
|
||||
|
||||
override suspend fun getActivationRemoteState(): VisaActivationRemoteState = withContext(dispatcherProvider.io) {
|
||||
val result = request {
|
||||
val authTokens =
|
||||
checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" }
|
||||
private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi)
|
||||
|
||||
visaApi.getRemoteActivationStatus(
|
||||
authHeader = authTokens.getAuthHeader(),
|
||||
request = ActivationStatusRequest(
|
||||
cardId = visaCardId.cardId,
|
||||
cardPublicKey = visaCardId.cardPublicKey,
|
||||
),
|
||||
).getOrThrow()
|
||||
override suspend fun getActivationRemoteState(): Either<VisaApiError, VisaActivationRemoteState> =
|
||||
withContext(dispatcherProvider.io) {
|
||||
request {
|
||||
val authTokens =
|
||||
checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" }
|
||||
|
||||
visaApi.getRemoteActivationStatus(
|
||||
authHeader = authTokens.getAuthHeader(),
|
||||
request = ActivationStatusRequest(
|
||||
cardId = visaCardId.cardId,
|
||||
cardPublicKey = visaCardId.cardPublicKey,
|
||||
),
|
||||
).getOrThrow()
|
||||
}.map {
|
||||
VisaActivationStatusConverterWithState.convert(it)
|
||||
}
|
||||
}
|
||||
|
||||
VisaActivationStatusConverterWithState.convert(result)
|
||||
}
|
||||
|
||||
override suspend fun getCardWalletAcceptanceData(
|
||||
request: VisaCardWalletDataToSignRequest,
|
||||
): VisaDataToSignByCardWallet = withContext(dispatcherProvider.io) {
|
||||
val result = request {
|
||||
): Either<VisaApiError, VisaDataToSignByCardWallet> = withContext(dispatcherProvider.io) {
|
||||
request {
|
||||
val authTokens =
|
||||
checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" }
|
||||
|
||||
|
|
@ -67,18 +71,18 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
cardWalletAddress = request.cardWalletAddress,
|
||||
),
|
||||
).getOrThrow()
|
||||
}.map {
|
||||
VisaDataToSignByCardWallet(
|
||||
request = request,
|
||||
hashToSign = it.result.hash,
|
||||
)
|
||||
}
|
||||
|
||||
VisaDataToSignByCardWallet(
|
||||
request = request,
|
||||
hashToSign = result.result.hash,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCustomerWalletAcceptanceData(
|
||||
request: VisaCustomerWalletDataToSignRequest,
|
||||
): VisaDataToSignByCustomerWallet = withContext(dispatcherProvider.io) {
|
||||
val result = request {
|
||||
): Either<VisaApiError, VisaDataToSignByCustomerWallet> = withContext(dispatcherProvider.io) {
|
||||
request {
|
||||
val authTokens =
|
||||
checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" }
|
||||
|
||||
|
|
@ -89,15 +93,15 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
customerWalletAddress = request.customerWalletAddress,
|
||||
),
|
||||
).getOrThrow()
|
||||
}.map {
|
||||
VisaDataToSignByCustomerWallet(
|
||||
request = request,
|
||||
hashToSign = it.result.hash,
|
||||
)
|
||||
}
|
||||
|
||||
VisaDataToSignByCustomerWallet(
|
||||
request = request,
|
||||
hashToSign = result.result.hash,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun activateCard(signedData: VisaSignedActivationDataByCardWallet) {
|
||||
override suspend fun activateCard(signedData: VisaSignedActivationDataByCardWallet): Either<VisaApiError, Unit> =
|
||||
withContext(dispatcherProvider.io) {
|
||||
request {
|
||||
val authTokens =
|
||||
|
|
@ -120,9 +124,8 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun approveByCustomerWallet(signedData: VisaSignedDataByCustomerWallet) {
|
||||
override suspend fun approveByCustomerWallet(signedData: VisaSignedDataByCustomerWallet) =
|
||||
withContext(dispatcherProvider.io) {
|
||||
request {
|
||||
val authTokens =
|
||||
|
|
@ -140,24 +143,21 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
request {
|
||||
val authTokens =
|
||||
checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" }
|
||||
override suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) = withContext(dispatcherProvider.io) {
|
||||
request {
|
||||
val authTokens =
|
||||
checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" }
|
||||
|
||||
visaApi.setPinCode(
|
||||
authHeader = authTokens.getAuthHeader(),
|
||||
body = SetPinCodeRequest(
|
||||
orderId = pinCode.activationOrderId,
|
||||
sessionId = pinCode.sessionId,
|
||||
iv = pinCode.iv,
|
||||
pin = pinCode.encryptedPin,
|
||||
),
|
||||
).getOrThrow()
|
||||
}
|
||||
visaApi.setPinCode(
|
||||
authHeader = authTokens.getAuthHeader(),
|
||||
body = SetPinCodeRequest(
|
||||
orderId = pinCode.activationOrderId,
|
||||
sessionId = pinCode.sessionId,
|
||||
iv = pinCode.iv,
|
||||
pin = pinCode.encryptedPin,
|
||||
),
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,24 +172,42 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun <T : Any> request(requestBlock: suspend () -> T): T {
|
||||
private suspend fun <T : Any> request(requestBlock: suspend () -> T): Either<VisaApiError, T> {
|
||||
return runCatching {
|
||||
requestBlock()
|
||||
Either.Right(requestBlock())
|
||||
}.getOrElse { responseError ->
|
||||
if (responseError !is ApiResponseError.HttpException ||
|
||||
responseError.code != ApiResponseError.HttpException.Code.UNAUTHORIZED
|
||||
if (responseError is ApiResponseError.HttpException &&
|
||||
responseError.code != ApiResponseError.HttpException.Code.UNAUTHORIZED &&
|
||||
responseError.errorBody != null
|
||||
) {
|
||||
throw responseError
|
||||
val errorCode =
|
||||
visaErrorAdapter.fromJson(responseError.errorBody!!)?.error?.code ?: responseError.code.numericCode
|
||||
return Either.Left(VisaApiError.fromBackendError(errorCode))
|
||||
}
|
||||
|
||||
if (responseError !is ApiResponseError.HttpException) {
|
||||
return Either.Left(VisaApiError.UnknownWithoutCode)
|
||||
}
|
||||
|
||||
val authTokens = visaAuthTokenStorage.get(visaCardId.cardId) ?: error("Auth tokens are not stored")
|
||||
val newTokens = runCatching {
|
||||
visaAuthRepository.refreshAccessTokens(authTokens.refreshToken)
|
||||
}.getOrElse { throw RefreshTokenExpiredException() }
|
||||
val newTokens = visaAuthRepository.refreshAccessTokens(authTokens.refreshToken).getOrElse {
|
||||
return Either.Left(VisaApiError.RefreshTokenExpired)
|
||||
}
|
||||
|
||||
visaAuthTokenStorage.store(visaCardId.cardId, newTokens)
|
||||
|
||||
requestBlock()
|
||||
runCatching {
|
||||
Either.Right(requestBlock())
|
||||
}.getOrElse { responseError ->
|
||||
if (responseError is ApiResponseError.HttpException && responseError.errorBody != null) {
|
||||
val errorCode =
|
||||
visaErrorAdapter.fromJson(responseError.errorBody!!)?.error?.code
|
||||
?: responseError.code.numericCode
|
||||
Either.Left(VisaApiError.fromBackendError(errorCode))
|
||||
} else {
|
||||
Either.Left(VisaApiError.UnknownWithoutCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
package com.tangem.data.visa
|
||||
|
||||
import arrow.core.Either
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.visa.TangemVisaApi
|
||||
import com.tangem.datasource.api.visa.models.request.*
|
||||
import com.tangem.datasource.api.visa.models.response.VisaErrorResponseJsonAdapter
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||
import com.tangem.domain.visa.model.VisaAuthSession
|
||||
import com.tangem.domain.visa.model.VisaAuthSignedChallenge
|
||||
|
|
@ -14,40 +20,54 @@ import javax.inject.Inject
|
|||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultVisaAuthRepository @Inject constructor(
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val visaAuthApi: TangemVisaApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : VisaAuthRepository {
|
||||
|
||||
override suspend fun getCardAuthChallenge(cardId: String, cardPublicKey: String): VisaAuthChallenge.Card =
|
||||
withContext(dispatchers.io) {
|
||||
val response = visaAuthApi.generateNonceByCardId(
|
||||
private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi)
|
||||
|
||||
override suspend fun getCardAuthChallenge(
|
||||
cardId: String,
|
||||
cardPublicKey: String,
|
||||
): Either<VisaApiError, VisaAuthChallenge.Card> = withContext(dispatchers.io) {
|
||||
request {
|
||||
visaAuthApi.generateNonceByCardId(
|
||||
GenerateNoneByCardIdRequest(
|
||||
cardId = cardId,
|
||||
cardPublicKey = cardPublicKey,
|
||||
),
|
||||
)
|
||||
).getOrThrow()
|
||||
}.map { response ->
|
||||
VisaAuthChallenge.Card(
|
||||
challenge = response.result.nonce,
|
||||
session = VisaAuthSession(response.result.sessionId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getCardWalletAuthChallenge(cardWalletAddress: String): VisaAuthChallenge.Wallet =
|
||||
withContext(dispatchers.io) {
|
||||
val response = visaAuthApi.generateNonceByCardWallet(
|
||||
override suspend fun getCardWalletAuthChallenge(
|
||||
cardWalletAddress: String,
|
||||
): Either<VisaApiError, VisaAuthChallenge.Wallet> = withContext(dispatchers.io) {
|
||||
request {
|
||||
visaAuthApi.generateNonceByCardWallet(
|
||||
GenerateNoneByCardWalletRequest(
|
||||
cardWalletAddress = cardWalletAddress,
|
||||
),
|
||||
)
|
||||
).getOrThrow()
|
||||
}.map { response ->
|
||||
VisaAuthChallenge.Wallet(
|
||||
challenge = response.result.nonce,
|
||||
session = VisaAuthSession(response.result.sessionId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): VisaAuthTokens =
|
||||
withContext(dispatchers.io) {
|
||||
val response = when (signedChallenge) {
|
||||
override suspend fun getAccessTokens(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): Either<VisaApiError, VisaAuthTokens> = withContext(dispatchers.io) {
|
||||
request {
|
||||
when (signedChallenge) {
|
||||
is VisaAuthSignedChallenge.ByCardPublicKey -> {
|
||||
visaAuthApi.getAccessTokenByCardId(
|
||||
GetAccessTokenByCardIdRequest(
|
||||
|
|
@ -55,7 +75,7 @@ internal class DefaultVisaAuthRepository @Inject constructor(
|
|||
signature = signedChallenge.signature,
|
||||
salt = signedChallenge.salt,
|
||||
),
|
||||
)
|
||||
).getOrThrow()
|
||||
}
|
||||
is VisaAuthSignedChallenge.ByWallet -> {
|
||||
visaAuthApi.getAccessTokenByCardWallet(
|
||||
|
|
@ -63,9 +83,10 @@ internal class DefaultVisaAuthRepository @Inject constructor(
|
|||
sessionId = signedChallenge.challenge.session.sessionId,
|
||||
signature = signedChallenge.signature,
|
||||
),
|
||||
)
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
}.map { response ->
|
||||
VisaAuthTokens(
|
||||
accessToken = response.result.accessToken,
|
||||
refreshToken = VisaAuthTokens.RefreshToken(
|
||||
|
|
@ -77,10 +98,13 @@ internal class DefaultVisaAuthRepository @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refreshAccessTokens(refreshToken: VisaAuthTokens.RefreshToken): VisaAuthTokens =
|
||||
withContext(dispatchers.io) {
|
||||
val response = when (refreshToken.authType) {
|
||||
override suspend fun refreshAccessTokens(
|
||||
refreshToken: VisaAuthTokens.RefreshToken,
|
||||
): Either<VisaApiError, VisaAuthTokens> = withContext(dispatchers.io) {
|
||||
request {
|
||||
when (refreshToken.authType) {
|
||||
VisaAuthTokens.RefreshToken.Type.CardId ->
|
||||
visaAuthApi.refreshCardIdAccessToken(
|
||||
RefreshTokenByCardIdRequest(refreshToken = refreshToken.value),
|
||||
|
|
@ -90,10 +114,27 @@ internal class DefaultVisaAuthRepository @Inject constructor(
|
|||
RefreshTokenByCardIdRequest(refreshToken = refreshToken.value),
|
||||
)
|
||||
}.getOrThrow()
|
||||
|
||||
}.map { response ->
|
||||
VisaAuthTokens(
|
||||
accessToken = response.result.accessToken,
|
||||
refreshToken = refreshToken.copy(value = response.result.refreshToken),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T : Any> request(requestBlock: suspend () -> T): Either<VisaApiError, T> {
|
||||
return runCatching {
|
||||
Either.Right(requestBlock())
|
||||
}.getOrElse { responseError ->
|
||||
if (responseError is ApiResponseError.HttpException &&
|
||||
responseError.errorBody != null
|
||||
) {
|
||||
val errorCode =
|
||||
visaErrorAdapter.fromJson(responseError.errorBody!!)?.error?.code ?: responseError.code.numericCode
|
||||
return Either.Left(VisaApiError.fromBackendError(errorCode))
|
||||
}
|
||||
|
||||
return Either.Left(VisaApiError.UnknownWithoutCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
package com.tangem.data.visa
|
||||
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
class MockVisaActivationRepository @AssistedInject constructor(
|
||||
@Assisted private val visaCardId: VisaCardId,
|
||||
) : VisaActivationRepository {
|
||||
|
||||
override suspend fun getActivationRemoteState(): VisaActivationRemoteState {
|
||||
return VisaActivationRemoteState.CardWalletSignatureRequired(
|
||||
activationOrderInfo = VisaActivationOrderInfo(
|
||||
orderId = "orderId",
|
||||
customerId = "customerId",
|
||||
customerWalletAddress = "customerWalletAddress",
|
||||
cardWalletAddress = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCardWalletAcceptanceData(
|
||||
request: VisaCardWalletDataToSignRequest,
|
||||
): VisaDataToSignByCardWallet {
|
||||
return VisaDataToSignByCardWallet(
|
||||
request = request,
|
||||
hashToSign = CryptoUtils.generateRandomBytes(length = 32).toHexString(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCustomerWalletAcceptanceData(
|
||||
request: VisaCustomerWalletDataToSignRequest,
|
||||
): VisaDataToSignByCustomerWallet {
|
||||
return VisaDataToSignByCustomerWallet(
|
||||
request = request,
|
||||
CryptoUtils.generateRandomBytes(length = 32).toHexString(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun activateCard(signedData: VisaSignedActivationDataByCardWallet) {}
|
||||
|
||||
override suspend fun approveByCustomerWallet(signedData: VisaSignedDataByCustomerWallet) {}
|
||||
|
||||
override suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) {}
|
||||
|
||||
override suspend fun getPinCodeRsaEncryptionPublicKey(): String {
|
||||
return CryptoUtils.generateRandomBytes(length = 32).toHexString()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : VisaActivationRepository.Factory {
|
||||
override fun create(cardId: VisaCardId): MockVisaActivationRepository
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue