Updated on 2026-08-14
This commit is contained in:
parent
ff55704d32
commit
9e2eb5710b
82 changed files with 1150 additions and 401 deletions
|
|
@ -23,6 +23,7 @@ dependencies {
|
|||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
|
@ -31,6 +32,7 @@ dependencies {
|
|||
implementation(projects.domain.networks)
|
||||
implementation(projects.domain.walletManager)
|
||||
implementation(projects.domain.quotes)
|
||||
implementation(projects.domain.common)
|
||||
|
||||
/** Feature API - remove after removing [HotWalletFeatureToggles] */
|
||||
implementation(projects.features.hotWallet.api)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import arrow.core.raise.either
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||
import com.tangem.domain.visa.model.TangemPayAuthTokens
|
||||
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -16,18 +16,14 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor(
|
|||
) : TangemPayAuthDataSource {
|
||||
|
||||
override suspend fun produceInitialCredentials(cardId: String): Either<Throwable, TangemPayInitialCredentials> {
|
||||
val initialCredentials = tangemSdkManager.tangemPayProduceInitialCredentials(cardId = cardId)
|
||||
|
||||
return when (initialCredentials) {
|
||||
return when (val initialCredentials = tangemSdkManager.tangemPayProduceInitialCredentials(cardId = cardId)) {
|
||||
is CompletionResult.Failure<*> -> Either.Left(initialCredentials.error)
|
||||
is CompletionResult.Success<TangemPayInitialCredentials> -> Either.Right(initialCredentials.data)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, VisaAuthTokens> = either {
|
||||
visaAuthRemoteDataSource.refreshCustomerWalletAuthTokens(
|
||||
VisaAuthTokens.RefreshToken(refreshToken, authType = VisaAuthTokens.RefreshToken.Type.CardWallet),
|
||||
)
|
||||
override suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, TangemPayAuthTokens> = either {
|
||||
visaAuthRemoteDataSource.refreshCustomerWalletAuthTokens(refreshToken = refreshToken)
|
||||
.mapLeft { IllegalStateException("TangemPay token refresh failed. Error code: ${it.errorCode}") }
|
||||
.bind()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.KycStartInfo
|
||||
import com.tangem.domain.pay.repository.KycRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "TangemPay: KycRepository"
|
||||
|
||||
internal class DefaultKycRepository @Inject constructor(
|
||||
private val tangemPayApi: TangemPayApi,
|
||||
private val requestHelper: TangemPayRequestPerformer,
|
||||
) : KycRepository {
|
||||
|
||||
override suspend fun getKycStartInfo(): Either<UniversalError, KycStartInfo> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getKycAccess(authHeader = authHeader)
|
||||
}.result
|
||||
|
||||
KycStartInfo(token = result.token, locale = result.locale)
|
||||
}
|
||||
override suspend fun getKycStartInfo(userWalletId: UserWalletId): Either<VisaApiError, KycStartInfo> {
|
||||
return requestHelper.performRequest(
|
||||
userWalletId,
|
||||
) { authHeader -> tangemPayApi.getKycAccess(authHeader = authHeader) }
|
||||
.map { KycStartInfo(token = it.result.token, locale = it.result.locale) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,29 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest
|
||||
import com.tangem.datasource.api.pay.models.request.OrderRequest
|
||||
import com.tangem.datasource.api.pay.models.response.CustomerMeResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
|
||||
import com.tangem.domain.pay.model.CustomerInfo.ProductInstance
|
||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val VALID_STATUS = "valid"
|
||||
|
|
@ -34,105 +37,82 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
private val requestHelper: TangemPayRequestPerformer,
|
||||
private val tangemPayStorage: TangemPayStorage,
|
||||
private val authDataSource: TangemPayAuthDataSource,
|
||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||
private val cardFrozenStateStore: TangemPayCardFrozenStateStore,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : OnboardingRepository {
|
||||
|
||||
// Save data for a session
|
||||
private var lastFetchedCustomerInfo: CustomerInfo? = null
|
||||
private val lastFetchedCustomerInfoMap = ConcurrentHashMap<UserWalletId, CustomerInfo>()
|
||||
|
||||
override suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link))
|
||||
.getOrThrow()
|
||||
.result
|
||||
result?.status == VALID_STATUS
|
||||
override suspend fun validateDeeplink(link: String): Either<VisaApiError, Boolean> {
|
||||
return requestHelper.performWithStaticToken {
|
||||
tangemPayApi.validateDeeplink(body = DeeplinkValidityRequest(link = link))
|
||||
}.map { response ->
|
||||
response.result?.status == VALID_STATUS
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isTangemPayInitialDataProduced(): Boolean {
|
||||
val walletId = tangemPayWalletsManager.getDefaultWalletForTangemPay().walletId
|
||||
val customerWalletAddress = tangemPayStorage.getCustomerWalletAddress(walletId) ?: return false
|
||||
tangemPayStorage.getAuthTokens(customerWalletAddress) ?: return false
|
||||
override suspend fun isTangemPayInitialDataProduced(userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
val customerWalletAddress =
|
||||
tangemPayStorage.getCustomerWalletAddress(userWalletId) ?: return@withContext false
|
||||
tangemPayStorage.getAuthTokens(customerWalletAddress) ?: return@withContext false
|
||||
|
||||
return true
|
||||
return@withContext true
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun produceInitialData() {
|
||||
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPay()
|
||||
val initialCredentials = authDataSource.produceInitialCredentials(cardId = wallet.cardId)
|
||||
.fold(
|
||||
ifLeft = { error -> error("Can not produce initial data: ${error.message}") },
|
||||
ifRight = { it },
|
||||
override suspend fun produceInitialData(userWalletId: UserWalletId) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
val initialCredentials = authDataSource.produceInitialCredentials(cardId = getCardId(userWalletId))
|
||||
.fold(
|
||||
ifLeft = { error -> error("Can not produce initial data: ${error.message}") },
|
||||
ifRight = { it },
|
||||
)
|
||||
// should storeCheckCustomerWalletResult because we already know this
|
||||
tangemPayStorage.storeCheckCustomerWalletResult(userWalletId)
|
||||
tangemPayStorage.storeCustomerWalletAddress(
|
||||
userWalletId = userWalletId,
|
||||
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||
)
|
||||
tangemPayStorage.storeAuthTokens(
|
||||
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||
tokens = initialCredentials.authTokens,
|
||||
)
|
||||
tangemPayStorage.storeCustomerWalletAddress(
|
||||
userWalletId = wallet.walletId,
|
||||
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||
)
|
||||
tangemPayStorage.storeAuthTokens(
|
||||
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||
tokens = initialCredentials.authTokens,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCustomerInfo(): Either<UniversalError, CustomerInfo> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getCustomerMe(authHeader)
|
||||
}.result
|
||||
getCustomerInfo(result)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMainScreenCustomerInfo(): Either<UniversalError, MainScreenCustomerInfo> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress()
|
||||
override suspend fun getCustomerInfo(userWalletId: UserWalletId): Either<VisaApiError, CustomerInfo> {
|
||||
// TODO implement selector
|
||||
return requestHelper.performRequest(userWalletId) { authHeader -> tangemPayApi.getCustomerMe(authHeader) }
|
||||
.map { response -> getCustomerInfo(userWalletId = userWalletId, response = response.result) }
|
||||
}
|
||||
|
||||
when (val orderId = tangemPayStorage.getOrderId(customerWalletAddress)) {
|
||||
// If order id wasn't saved -> start order creation and get customer info
|
||||
null -> {
|
||||
createOrder()
|
||||
MainScreenCustomerInfo(
|
||||
info = getCustomerInfoWithPersistedToken(),
|
||||
orderStatus = OrderStatus.UNKNOWN,
|
||||
)
|
||||
}
|
||||
// If order id was saved -> check its status
|
||||
else -> {
|
||||
val orderStatus = getOrderStatus(orderId)
|
||||
if (orderStatus == OrderStatus.CANCELED) {
|
||||
// If order was cancelled -> start order creation
|
||||
createOrder()
|
||||
}
|
||||
val customerInfo = when (orderStatus) {
|
||||
// Kyc is passed and user waits for order creation -> no need to get customer info
|
||||
OrderStatus.NEW,
|
||||
OrderStatus.PROCESSING,
|
||||
-> CustomerInfo(productInstance = null, isKycApproved = true, cardInfo = null)
|
||||
|
||||
// Order was created/cancelled -> clear order id and get customer info
|
||||
OrderStatus.UNKNOWN,
|
||||
OrderStatus.COMPLETED,
|
||||
OrderStatus.CANCELED,
|
||||
-> getCustomerInfoWithPersistedToken().also {
|
||||
tangemPayStorage.clearOrderId(customerWalletAddress)
|
||||
}
|
||||
}
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = orderStatus)
|
||||
}
|
||||
}
|
||||
override suspend fun clearOrderId(userWalletId: UserWalletId) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId)
|
||||
tangemPayStorage.clearOrderId(customerWalletAddress = customerWalletAddress)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSavedCustomerInfo(): CustomerInfo? {
|
||||
return lastFetchedCustomerInfo
|
||||
override suspend fun getOrderId(userWalletId: UserWalletId): String? {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId)
|
||||
tangemPayStorage.getOrderId(customerWalletAddress)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createOrder() = withContext(dispatcherProvider.io) {
|
||||
override fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo? {
|
||||
return lastFetchedCustomerInfoMap[userWalletId]
|
||||
}
|
||||
|
||||
override suspend fun createOrder(userWalletId: UserWalletId) = withContext(dispatcherProvider.io) {
|
||||
launch {
|
||||
requestHelper.runWithErrorLogs(TAG) {
|
||||
val walletAddress = requestHelper.getCustomerWalletAddress()
|
||||
val result = requestHelper.request { authHeader ->
|
||||
val walletAddress = requestHelper.getCustomerWalletAddress(userWalletId)
|
||||
val result = requestHelper.request(userWalletId) { authHeader ->
|
||||
tangemPayApi.createOrder(authHeader, body = OrderRequest(walletAddress))
|
||||
}.result ?: error("Create order result is null")
|
||||
|
||||
|
|
@ -141,7 +121,23 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getCustomerInfo(response: CustomerMeResponse.Result?): CustomerInfo {
|
||||
private fun getCardId(userWalletId: UserWalletId): String {
|
||||
val userWallet = if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
userWalletsListRepository.userWallets.value?.firstOrNull { it.walletId == userWalletId }
|
||||
} else {
|
||||
userWalletsListManager.userWalletsSync.firstOrNull { it.walletId == userWalletId }
|
||||
} ?: error("no userWallet found")
|
||||
return if (userWallet is UserWallet.Cold) {
|
||||
userWallet.cardId
|
||||
} else {
|
||||
TODO("[REDACTED_JIRA]")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCustomerInfo(
|
||||
userWalletId: UserWalletId,
|
||||
response: CustomerMeResponse.Result?,
|
||||
): CustomerInfo {
|
||||
val card = response?.card
|
||||
val balance = response?.balance
|
||||
val paymentAccount = response?.paymentAccount
|
||||
|
|
@ -157,46 +153,59 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
null
|
||||
}
|
||||
val productInstance = response?.productInstance?.let { instance ->
|
||||
cardFrozenStateStore.store(
|
||||
key = instance.cardId,
|
||||
value = when (instance.status) {
|
||||
CustomerMeResponse.ProductInstance.Status.ACTIVE -> TangemPayCardFrozenState.Unfrozen
|
||||
else -> TangemPayCardFrozenState.Frozen
|
||||
},
|
||||
)
|
||||
ProductInstance(
|
||||
id = instance.id,
|
||||
cardId = instance.cardId,
|
||||
status = when (instance.status) {
|
||||
CustomerMeResponse.ProductInstance.Status.ACTIVE -> ProductInstance.Status.ACTIVE
|
||||
else -> ProductInstance.Status.INACTIVE
|
||||
},
|
||||
)
|
||||
val cardFrozenState = when (instance.status) {
|
||||
CustomerMeResponse.ProductInstance.Status.ACTIVE -> TangemPayCardFrozenState.Unfrozen
|
||||
else -> TangemPayCardFrozenState.Frozen
|
||||
}
|
||||
cardFrozenStateStore.store(key = instance.cardId, value = cardFrozenState)
|
||||
|
||||
ProductInstance(id = instance.id, cardId = instance.cardId, cardFrozenState = cardFrozenState)
|
||||
}
|
||||
return CustomerInfo(
|
||||
productInstance = productInstance,
|
||||
isKycApproved = response?.kyc?.status == APPROVED_KYC_STATUS,
|
||||
cardInfo = cardInfo,
|
||||
).also { lastFetchedCustomerInfo = it }
|
||||
}
|
||||
|
||||
private suspend fun getOrderStatus(orderId: String): OrderStatus {
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getOrder(authHeader, orderId)
|
||||
}.result ?: error("Order result is null")
|
||||
|
||||
return when (result.status) {
|
||||
OrderStatus.NEW.apiName -> OrderStatus.NEW
|
||||
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
|
||||
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
|
||||
else -> OrderStatus.CANCELED
|
||||
).also {
|
||||
lastFetchedCustomerInfoMap[userWalletId] = it
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCustomerInfoWithPersistedToken(): CustomerInfo {
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getCustomerMe(authHeader)
|
||||
}.result
|
||||
return getCustomerInfo(result)
|
||||
override suspend fun getOrderStatus(
|
||||
userWalletId: UserWalletId,
|
||||
orderId: String,
|
||||
): Either<VisaApiError, OrderStatus> {
|
||||
return requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.getOrder(authHeader = authHeader, orderId = orderId)
|
||||
}.map { response ->
|
||||
when (response.result?.status) {
|
||||
null -> OrderStatus.UNKNOWN
|
||||
OrderStatus.NEW.apiName -> OrderStatus.NEW
|
||||
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
|
||||
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
|
||||
else -> OrderStatus.CANCELED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean> {
|
||||
val hasTangemPay = tangemPayStorage.checkCustomerWalletResult(userWalletId)
|
||||
if (hasTangemPay == true) {
|
||||
return Either.Right(true)
|
||||
}
|
||||
|
||||
return requestHelper.performWithStaticToken { staticToken ->
|
||||
tangemPayApi.checkCustomerWalletId(
|
||||
authHeader = staticToken,
|
||||
customerWalletId = userWalletId.stringValue,
|
||||
)
|
||||
}.map { response ->
|
||||
val id = response.id
|
||||
if (!id.isNullOrEmpty()) {
|
||||
tangemPayStorage.storeCheckCustomerWalletResult(userWalletId = userWalletId)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import com.tangem.datasource.api.pay.models.request.SetPinRequest
|
|||
import com.tangem.datasource.api.pay.models.response.FreezeUnfreezeCardResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.SetPinResult
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.pay.model.TangemPayCardDetails
|
||||
|
|
@ -35,9 +36,9 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
private val cardFrozenStateStore: TangemPayCardFrozenStateStore,
|
||||
) : TangemPayCardDetailsRepository {
|
||||
|
||||
override suspend fun getCardBalance(): Either<UniversalError, TangemPayCardBalance> {
|
||||
override suspend fun getCardBalance(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardBalance> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = requestHelper.request { authHeader ->
|
||||
val result = requestHelper.request(userWalletId) { authHeader ->
|
||||
tangemPayApi.getCardBalance(authHeader)
|
||||
}.result ?: error("Cannot get card balance")
|
||||
TangemPayCardBalance(
|
||||
|
|
@ -47,12 +48,12 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun revealCardDetails(): Either<UniversalError, TangemPayCardDetails> {
|
||||
override suspend fun revealCardDetails(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardDetails> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val publicKeyBase64 = getPublicKeyBase64()
|
||||
val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64)
|
||||
|
||||
val result = requestHelper.request { authHeader ->
|
||||
val result = requestHelper.request(userWalletId) { authHeader ->
|
||||
tangemPayApi.revealCardDetails(
|
||||
authHeader = authHeader,
|
||||
body = CardDetailsRequest(sessionId = sessionId),
|
||||
|
|
@ -81,14 +82,14 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun setPin(pin: String): Either<UniversalError, SetPinResult> {
|
||||
override suspend fun setPin(userWalletId: UserWalletId, pin: String): Either<UniversalError, SetPinResult> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val publicKeyBase64 = getPublicKeyBase64()
|
||||
val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64)
|
||||
val encryptedData = rainCryptoUtil.encryptPin(pin = pin, secretKeyBytes = secretKeyBytes)
|
||||
secretKeyBytes.fill(0)
|
||||
|
||||
val status = requestHelper.request { authHeader ->
|
||||
val status = requestHelper.request(userWalletId) { authHeader ->
|
||||
tangemPayApi.setPin(
|
||||
authHeader = authHeader,
|
||||
body = SetPinRequest(
|
||||
|
|
@ -107,21 +108,24 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun isAddToWalletDone(): Either<UniversalError, Boolean> {
|
||||
override suspend fun isAddToWalletDone(userWalletId: UserWalletId): Either<UniversalError, Boolean> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
storage.getAddToWalletDone(requestHelper.getCustomerWalletAddress())
|
||||
storage.getAddToWalletDone(requestHelper.getCustomerWalletAddress(userWalletId))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setAddToWalletAsDone(): Either<UniversalError, Unit> {
|
||||
override suspend fun setAddToWalletAsDone(userWalletId: UserWalletId): Either<UniversalError, Unit> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
storage.storeAddToWalletDone(requestHelper.getCustomerWalletAddress(), isDone = true)
|
||||
storage.storeAddToWalletDone(requestHelper.getCustomerWalletAddress(userWalletId), isDone = true)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun freezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState> {
|
||||
override suspend fun freezeCard(
|
||||
userWalletId: UserWalletId,
|
||||
cardId: String,
|
||||
): Either<UniversalError, TangemPayCardFrozenState> {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Pending)
|
||||
return requestHelper.makeSafeRequest {
|
||||
return requestHelper.makeSafeRequest(userWalletId) {
|
||||
tangemPayApi.freezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId))
|
||||
}.onLeft {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Unfrozen)
|
||||
|
|
@ -141,9 +145,12 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun unfreezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState> {
|
||||
override suspend fun unfreezeCard(
|
||||
userWalletId: UserWalletId,
|
||||
cardId: String,
|
||||
): Either<UniversalError, TangemPayCardFrozenState> {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Pending)
|
||||
return requestHelper.makeSafeRequest {
|
||||
return requestHelper.makeSafeRequest(userWalletId) {
|
||||
tangemPayApi.unfreezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId))
|
||||
}.onLeft {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Frozen)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.data.visa.utils.TangemPayTxHistoryItemConverter
|
|||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListConfig
|
||||
|
|
@ -34,6 +35,7 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
private val txHistoryItemConverter by lazy { TangemPayTxHistoryItemConverter(moshi) }
|
||||
|
||||
override fun getTxHistoryBatchFlow(
|
||||
userWalletId: UserWalletId,
|
||||
batchSize: Int,
|
||||
context: TangemPayTxHistoryListBatchingContext,
|
||||
): TangemPayTxHistoryListBatchFlow {
|
||||
|
|
@ -41,18 +43,24 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
fetchDispatcher = dispatchers.io,
|
||||
context = context,
|
||||
generateNewKey = { keys -> keys.lastOrNull()?.inc() ?: 0 },
|
||||
batchFetcher = createFetcher(batchSize),
|
||||
batchFetcher = createFetcher(userWalletId, batchSize),
|
||||
).toBatchFlow()
|
||||
}
|
||||
|
||||
private fun createFetcher(
|
||||
userWalletId: UserWalletId,
|
||||
batchSize: Int,
|
||||
): BatchFetcher<TangemPayTxHistoryListConfig, List<TangemPayTxHistoryItem>> {
|
||||
return CursorBatchFetcher(
|
||||
prefetchDistance = batchSize,
|
||||
batchSize = batchSize,
|
||||
subFetcher = { request, _, _ ->
|
||||
val items = loadItems(config = request.params, cursor = request.cursor, limit = request.limit)
|
||||
val items = loadItems(
|
||||
userWalletId = userWalletId,
|
||||
config = request.params,
|
||||
cursor = request.cursor,
|
||||
limit = request.limit,
|
||||
)
|
||||
BatchFetchResult.Success(
|
||||
data = items,
|
||||
last = items.size < request.limit,
|
||||
|
|
@ -64,6 +72,7 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun loadItems(
|
||||
userWalletId: UserWalletId,
|
||||
config: TangemPayTxHistoryListConfig,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
|
|
@ -71,7 +80,14 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
cacheRegistry.invokeOnExpire(
|
||||
key = getCacheKey(customerWalletAddress = config.customerWalletAddress, cursor = cursor),
|
||||
skipCache = config.shouldRefresh,
|
||||
block = { fetch(customerWalletAddress = config.customerWalletAddress, cursor = cursor, pageSize = limit) },
|
||||
block = {
|
||||
fetch(
|
||||
userWalletId = userWalletId,
|
||||
customerWalletAddress = config.customerWalletAddress,
|
||||
cursor = cursor,
|
||||
pageSize = limit,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
return txHistoryItemsStore.getSyncOrNull(
|
||||
|
|
@ -84,9 +100,14 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
return "tangem_pay_tx_history_${customerWalletAddress}_${cursor ?: INITIAL_CURSOR}"
|
||||
}
|
||||
|
||||
private suspend fun fetch(customerWalletAddress: String, cursor: String?, pageSize: Int) {
|
||||
private suspend fun fetch(
|
||||
userWalletId: UserWalletId,
|
||||
customerWalletAddress: String,
|
||||
cursor: String?,
|
||||
pageSize: Int,
|
||||
) {
|
||||
requestPerformer.runWithErrorLogs(TAG) {
|
||||
val result = requestPerformer.request { authHeader ->
|
||||
val result = requestPerformer.request(userWalletId) { authHeader ->
|
||||
visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor)
|
||||
}.result
|
||||
val items = txHistoryItemConverter.convertList(result.transactions).filterNotNull()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.right
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.squareup.wire.Instant
|
||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.domain.visa.model.TangemPayAuthTokens
|
||||
import com.tangem.domain.visa.model.getAuthHeader
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.*
|
||||
|
|
@ -25,20 +26,21 @@ import javax.inject.Inject
|
|||
|
||||
internal class TangemPayRequestPerformer @Inject constructor(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemPayStorage: TangemPayStorage,
|
||||
private val authDataSource: TangemPayAuthDataSource,
|
||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||
) {
|
||||
|
||||
private val customerWalletAddress = MutableStateFlow<String?>(null)
|
||||
|
||||
private val refreshTokensMutex = Mutex()
|
||||
private var refreshTokensJob: Deferred<VisaAuthTokens>? = null
|
||||
private var refreshTokensJob: Deferred<TangemPayAuthTokens>? = null
|
||||
|
||||
private val errorConverter = TangemPayErrorConverter(moshi)
|
||||
|
||||
suspend fun <T : Any> runWithErrorLogs(tag: String, requestBlock: suspend () -> T): Either<UniversalError, T> {
|
||||
@Deprecated("Do not use this method")
|
||||
suspend fun <T : Any> runWithErrorLogs(tag: String, requestBlock: suspend () -> T): Either<VisaApiError, T> {
|
||||
return try {
|
||||
val result = requestBlock()
|
||||
Either.Right(result)
|
||||
|
|
@ -56,54 +58,81 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
}
|
||||
|
||||
suspend fun <T : Any> makeSafeRequest(
|
||||
userWalletId: UserWalletId,
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
): Either<VisaApiError, T> {
|
||||
return catch { request(requestBlock) }
|
||||
.mapLeft { exception ->
|
||||
Timber.tag("TangemPayRequestPerformer").e(exception)
|
||||
errorConverter.convert(exception)
|
||||
}
|
||||
return performRequest(userWalletId, requestBlock)
|
||||
}
|
||||
|
||||
suspend fun <T : Any> request(requestBlock: suspend (header: String) -> ApiResponse<T>): T =
|
||||
withContext(dispatchers.io) {
|
||||
performRequest(
|
||||
requestBlock = requestBlock,
|
||||
getTokens = ::getAccessTokens,
|
||||
refreshTokens = ::refreshAuthTokens,
|
||||
@Deprecated("Use perform request instead", replaceWith = ReplaceWith("performRequest"))
|
||||
suspend fun <T : Any> request(
|
||||
userWalletId: UserWalletId,
|
||||
requestBlock: suspend (header: String) ->
|
||||
ApiResponse<T>,
|
||||
): T = withContext(dispatchers.io) {
|
||||
performRequest(userWalletId, requestBlock = requestBlock)
|
||||
// to keep behaviour as previous
|
||||
.fold(
|
||||
ifRight = { it },
|
||||
ifLeft = { error -> error("Cannot perform request: $error") },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun <T : Any> performRequest(
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
getTokens: (suspend () -> VisaAuthTokens),
|
||||
refreshTokens: (suspend () -> VisaAuthTokens)? = null,
|
||||
): T = runCatching {
|
||||
val tokens = getTokens()
|
||||
val header = tokens.getAuthHeader()
|
||||
requestBlock(header).getOrThrow()
|
||||
}.getOrElse { error ->
|
||||
val unauthorizedCode = ApiResponseError.HttpException.Code.UNAUTHORIZED
|
||||
if (error is ApiResponseError.HttpException && refreshTokens != null && error.code == unauthorizedCode) {
|
||||
refreshOrJoin(refreshTokens)
|
||||
performRequest(requestBlock, refreshTokens = null, getTokens = getTokens)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshOrJoin(refreshTokens: suspend () -> VisaAuthTokens): VisaAuthTokens {
|
||||
val jobToAwait: Deferred<VisaAuthTokens> =
|
||||
refreshTokensMutex.withLock {
|
||||
val current = refreshTokensJob
|
||||
if (current == null || current.isCompleted) {
|
||||
coroutineScope {
|
||||
async { refreshTokens() }.also { refreshTokensJob = it }
|
||||
}
|
||||
} else {
|
||||
current
|
||||
suspend fun <T : Any> performWithStaticToken(
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
): Either<VisaApiError, T> = withContext(dispatchers.io) {
|
||||
catch(
|
||||
block = {
|
||||
val staticToken =
|
||||
environmentConfigStorage.getConfigSync().bffStaticToken ?: error("BFF static token is null")
|
||||
when (val apiResponse = requestBlock(staticToken)) {
|
||||
is ApiResponse.Error -> errorConverter.convert(apiResponse.cause).left()
|
||||
is ApiResponse.Success<T> -> apiResponse.data.right()
|
||||
}
|
||||
},
|
||||
catch = { errorConverter.convert(it).left() },
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T : Any> performRequest(
|
||||
userWalletId: UserWalletId,
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
): Either<VisaApiError, T> = withContext(dispatchers.io) {
|
||||
catch(
|
||||
block = {
|
||||
val tokens = getAccessTokens(userWalletId)
|
||||
val now = Instant.now()
|
||||
val accessExpiresAt = Instant.ofEpochSecond(tokens.expiresAt)
|
||||
val refreshExpiresAt = Instant.ofEpochSecond(tokens.refreshExpiresAt)
|
||||
val apiResponse: ApiResponse<T> = if (accessExpiresAt.isAfter(now)) {
|
||||
requestBlock(tokens.getAuthHeader())
|
||||
} else if (accessExpiresAt.isBefore(now) && refreshExpiresAt.isAfter(now)) {
|
||||
val newTokens = refreshOrJoin(refreshTokens = { refreshAuthTokens(userWalletId) })
|
||||
requestBlock(newTokens.getAuthHeader())
|
||||
} else {
|
||||
return@catch VisaApiError.RefreshTokenExpired.left()
|
||||
}
|
||||
|
||||
when (apiResponse) {
|
||||
is ApiResponse.Error -> errorConverter.convert(apiResponse.cause).left()
|
||||
is ApiResponse.Success<T> -> apiResponse.data.right()
|
||||
}
|
||||
},
|
||||
catch = { errorConverter.convert(it).left() },
|
||||
).onLeft { visaApiError -> Timber.tag("TangemPayRequestPerformer").e(visaApiError.toString()) }
|
||||
}
|
||||
|
||||
private suspend fun refreshOrJoin(refreshTokens: suspend () -> TangemPayAuthTokens): TangemPayAuthTokens {
|
||||
val jobToAwait: Deferred<TangemPayAuthTokens> = refreshTokensMutex.withLock {
|
||||
val current = refreshTokensJob
|
||||
if (current == null || current.isCompleted) {
|
||||
coroutineScope {
|
||||
async { refreshTokens() }.also { refreshTokensJob = it }
|
||||
}
|
||||
} else {
|
||||
current
|
||||
}
|
||||
}
|
||||
val result = try {
|
||||
jobToAwait.await()
|
||||
} finally {
|
||||
|
|
@ -116,28 +145,28 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
return result
|
||||
}
|
||||
|
||||
suspend fun getCustomerWalletAddress(): String {
|
||||
suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String {
|
||||
val existingAddress = customerWalletAddress.value
|
||||
if (existingAddress != null) {
|
||||
return existingAddress
|
||||
}
|
||||
val storedAddress = tangemPayStorage.getCustomerWalletAddress(
|
||||
userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPay().walletId,
|
||||
userWalletId = userWalletId,
|
||||
) ?: error("Can not find customer address")
|
||||
|
||||
customerWalletAddress.value = storedAddress
|
||||
return storedAddress
|
||||
}
|
||||
|
||||
private suspend fun getAccessTokens(): VisaAuthTokens {
|
||||
val walletAddress = getCustomerWalletAddress()
|
||||
private suspend fun getAccessTokens(userWalletId: UserWalletId): TangemPayAuthTokens {
|
||||
val walletAddress = getCustomerWalletAddress(userWalletId)
|
||||
val tokens = tangemPayStorage.getAuthTokens(walletAddress) ?: error("Auth tokens are not stored")
|
||||
return tokens
|
||||
}
|
||||
|
||||
private suspend fun refreshAuthTokens(): VisaAuthTokens {
|
||||
val customerWalletAddress = getCustomerWalletAddress()
|
||||
val refreshToken = getAccessTokens().refreshToken.value
|
||||
private suspend fun refreshAuthTokens(userWalletId: UserWalletId): TangemPayAuthTokens {
|
||||
val customerWalletAddress = getCustomerWalletAddress(userWalletId)
|
||||
val refreshToken = getAccessTokens(userWalletId).refreshToken
|
||||
val tokens = authDataSource.refreshAuthTokens(refreshToken)
|
||||
.fold(
|
||||
ifLeft = { error -> error("Cannot refresh tokens: ${error.message}") },
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ class TangemPayErrorConverter(moshi: Moshi) : Converter<Throwable, VisaApiError>
|
|||
|
||||
override fun convert(value: Throwable): VisaApiError {
|
||||
return if (value is ApiResponseError.HttpException) {
|
||||
if (value.code == ApiResponseError.HttpException.Code.NOT_FOUND) return VisaApiError.NotPaeraCustomer
|
||||
|
||||
val errorBody = value.errorBody ?: return VisaApiError.UnknownWithoutCode
|
||||
return runCatching {
|
||||
visaErrorAdapter.fromJson(errorBody)?.error?.code ?: value.code.numericCode
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import kotlinx.coroutines.flow.filter
|
|||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class TangemPayWalletsManager @Inject constructor(
|
||||
// TODO remove after implement wallet selector in pay
|
||||
class TangemPayWalletsManager @Inject constructor(
|
||||
private val manager: UserWalletsListManager,
|
||||
private val repository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
|
|
|
|||
|
|
@ -5,15 +5,13 @@ 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.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.pay.TangemPayAuthApi
|
||||
import com.tangem.datasource.api.pay.models.request.*
|
||||
import com.tangem.datasource.api.pay.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
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
|
@ -21,6 +19,7 @@ import javax.inject.Inject
|
|||
internal class DefaultVisaAuthRemoteDataSource @Inject constructor(
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val visaAuthApi: TangemPayApi,
|
||||
private val tangemPayAuthApi: TangemPayAuthApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : VisaAuthRemoteDataSource {
|
||||
|
||||
|
|
@ -66,15 +65,19 @@ internal class DefaultVisaAuthRemoteDataSource @Inject constructor(
|
|||
|
||||
override suspend fun getCustomerWalletAuthChallenge(
|
||||
customerWalletAddress: String,
|
||||
customerWalletId: String,
|
||||
): Either<VisaApiError, VisaAuthChallenge.Wallet> = withContext(dispatchers.io) {
|
||||
request {
|
||||
visaAuthApi.generateNonceByCustomerWallet(
|
||||
GenerateNonceByCustomerWalletRequest(customerWalletAddress = customerWalletAddress),
|
||||
tangemPayAuthApi.generateNonceByCustomerWallet(
|
||||
request = GenerateNonceByCustomerWalletRequest(
|
||||
customerWalletAddress = customerWalletAddress,
|
||||
customerWalletId = customerWalletId,
|
||||
),
|
||||
).getOrThrow()
|
||||
}.map { response ->
|
||||
VisaAuthChallenge.Wallet(
|
||||
challenge = response.result.nonce,
|
||||
session = VisaAuthSession(response.result.sessionId),
|
||||
challenge = response.nonce,
|
||||
session = VisaAuthSession(response.sessionId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,37 +86,42 @@ internal class DefaultVisaAuthRemoteDataSource @Inject constructor(
|
|||
sessionId: String,
|
||||
signature: String,
|
||||
nonce: String,
|
||||
): Either<VisaApiError, VisaAuthTokens> = withContext(dispatchers.io) {
|
||||
): Either<VisaApiError, TangemPayAuthTokens> = withContext(dispatchers.io) {
|
||||
request {
|
||||
visaAuthApi.getTokenByCustomerWallet(
|
||||
GetTokenByCustomerWalletRequest(
|
||||
tangemPayAuthApi.getTokenByCustomerWallet(
|
||||
request = GetTokenByCustomerWalletRequest(
|
||||
authType = "customer_wallet",
|
||||
sessionId = sessionId,
|
||||
signature = signature,
|
||||
messageFormat = "Tangem Pay wants to sign in with your account. Nonce: $nonce",
|
||||
),
|
||||
).getOrThrow()
|
||||
}.map { response ->
|
||||
VisaAuthTokens(
|
||||
response.result.accessToken,
|
||||
VisaAuthTokens.RefreshToken(
|
||||
response.result.refreshToken,
|
||||
VisaAuthTokens.RefreshToken.Type.CardWallet,
|
||||
),
|
||||
TangemPayAuthTokens(
|
||||
accessToken = response.accessToken,
|
||||
expiresAt = response.expiresAt,
|
||||
refreshToken = response.refreshToken,
|
||||
refreshExpiresAt = response.refreshExpiresAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refreshCustomerWalletAuthTokens(
|
||||
refreshToken: VisaAuthTokens.RefreshToken,
|
||||
): Either<VisaApiError, VisaAuthTokens> = withContext(dispatchers.io) {
|
||||
refreshToken: String,
|
||||
): Either<VisaApiError, TangemPayAuthTokens> = withContext(dispatchers.io) {
|
||||
request {
|
||||
visaAuthApi.refreshCustomerWalletAccessToken(
|
||||
RefreshCustomerWalletAccessTokenRequest(refreshToken = refreshToken.value),
|
||||
tangemPayAuthApi.refreshCustomerWalletAccessToken(
|
||||
request = RefreshCustomerWalletAccessTokenRequest(
|
||||
authType = "customer_wallet",
|
||||
refreshToken = refreshToken,
|
||||
),
|
||||
).getOrThrow()
|
||||
}.map { response ->
|
||||
VisaAuthTokens(
|
||||
accessToken = response.result.accessToken,
|
||||
refreshToken = refreshToken.copy(value = response.result.refreshToken),
|
||||
TangemPayAuthTokens(
|
||||
accessToken = response.accessToken,
|
||||
expiresAt = response.expiresAt,
|
||||
refreshToken = response.refreshToken,
|
||||
refreshExpiresAt = response.refreshExpiresAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue