Updated on 2026-08-14
This commit is contained in:
parent
a10c1a8505
commit
710e944eae
14 changed files with 238 additions and 38 deletions
|
|
@ -7,7 +7,7 @@ import java.math.BigDecimal
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BigDecimalAdapter {
|
||||
internal class BigDecimalAdapter {
|
||||
@FromJson
|
||||
fun fromJson(value: String) = BigDecimal(value)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.datasource.api.common
|
||||
|
||||
import com.squareup.moshi.*
|
||||
import org.joda.time.DateTime
|
||||
|
||||
internal class DateTimeAdapter : JsonAdapter<DateTime>() {
|
||||
|
||||
@FromJson
|
||||
override fun fromJson(reader: JsonReader): DateTime? {
|
||||
val dateString = reader.nextString() ?: return null
|
||||
|
||||
return DateTime.parse(dateString)
|
||||
}
|
||||
|
||||
@ToJson
|
||||
override fun toJson(writer: JsonWriter, value: DateTime?) {
|
||||
if (value != null) {
|
||||
writer.value(value.toString())
|
||||
} else {
|
||||
writer.nullValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
package com.tangem.datasource.api.common
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
import com.squareup.moshi.ToJson
|
||||
import com.squareup.moshi.*
|
||||
import org.joda.time.LocalDate
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
|
||||
class LocalDateAdapter : JsonAdapter<LocalDate>() {
|
||||
internal class LocalDateAdapter : JsonAdapter<LocalDate>() {
|
||||
|
||||
private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd")
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import retrofit2.Retrofit
|
|||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
internal class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory() {
|
||||
class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory() {
|
||||
|
||||
override fun get(returnType: Type, annotations: Array<out Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
|
||||
if (getRawType(returnType) != Call::class.java) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.Moshi
|
|||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.datasource.api.common.BigDecimalAdapter
|
||||
import com.tangem.datasource.api.common.DateTimeAdapter
|
||||
import com.tangem.datasource.api.common.LocalDateAdapter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -22,6 +23,7 @@ class MoshiModule {
|
|||
return Moshi.Builder()
|
||||
.add(BigDecimalAdapter())
|
||||
.add(LocalDateAdapter())
|
||||
.add(DateTimeAdapter())
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.data.visa.utils
|
||||
|
||||
import com.tangem.domain.visa.model.VisaCurrency
|
||||
import com.tangem.lib.visa.model.BalancesAndLimits
|
||||
import com.tangem.lib.visa.model.VisaBalancesAndLimits
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.Instant
|
||||
|
|
@ -10,7 +10,7 @@ import java.math.BigInteger
|
|||
|
||||
internal class VisaCurrencyFactory {
|
||||
|
||||
fun create(balancesAndLimits: BalancesAndLimits, fiatRate: BigDecimal?): VisaCurrency {
|
||||
fun create(balancesAndLimits: VisaBalancesAndLimits, fiatRate: BigDecimal?): VisaCurrency {
|
||||
val now = Instant.now()
|
||||
val currentLimit = if (balancesAndLimits.limitsChangeDate > now) {
|
||||
balancesAndLimits.oldLimits
|
||||
|
|
@ -43,7 +43,7 @@ internal class VisaCurrencyFactory {
|
|||
)
|
||||
}
|
||||
|
||||
private fun getRemainingOtp(currentLimit: BalancesAndLimits.Limits, now: Instant): BigDecimal {
|
||||
private fun getRemainingOtp(currentLimit: VisaBalancesAndLimits.Limits, now: Instant): BigDecimal {
|
||||
if (currentLimit.expirationDate >= now) {
|
||||
return currentLimit.spendLimit.limit - currentLimit.spendLimit.spent
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ internal class VisaCurrencyFactory {
|
|||
return currentLimit.spendLimit.limit
|
||||
}
|
||||
|
||||
private fun getRemainingNoOtp(currentLimit: BalancesAndLimits.Limits, now: Instant): BigDecimal {
|
||||
private fun getRemainingNoOtp(currentLimit: VisaBalancesAndLimits.Limits, now: Instant): BigDecimal {
|
||||
if (currentLimit.expirationDate >= now) {
|
||||
return currentLimit.noOtpLimit.limit - currentLimit.noOtpLimit.spent
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ internal class VisaCurrencyFactory {
|
|||
return currentLimit.noOtpLimit.limit
|
||||
}
|
||||
|
||||
private fun getLimitsExpirationDate(currentLimits: BalancesAndLimits.Limits, now: Instant): DateTime {
|
||||
private fun getLimitsExpirationDate(currentLimits: VisaBalancesAndLimits.Limits, now: Instant): DateTime {
|
||||
val expirationDate = if (currentLimits.expirationDate >= now) {
|
||||
currentLimits.expirationDate.toDateTime()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.libs.visa"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Project */
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Libs - Network */
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.okHttp)
|
||||
implementation(deps.okHttp.prettyLogging)
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
|
||||
/** Libs - Other */
|
||||
implementation(deps.web3j.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.fx)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.okHttp.prettyLogging)
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.lib.visa
|
||||
|
||||
import arrow.fx.coroutines.parZip
|
||||
import com.tangem.lib.visa.model.BalancesAndLimits
|
||||
import com.tangem.lib.visa.model.BalancesAndLimits.Balances
|
||||
import com.tangem.lib.visa.model.BalancesAndLimits.Limits
|
||||
import com.tangem.lib.visa.model.VisaBalancesAndLimits
|
||||
import com.tangem.lib.visa.model.VisaBalancesAndLimits.Balances
|
||||
import com.tangem.lib.visa.model.VisaBalancesAndLimits.Limits
|
||||
import com.tangem.lib.visa.utils.toBigDecimal
|
||||
import com.tangem.lib.visa.utils.toInstant
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -21,7 +21,8 @@ internal class DefaultVisaContractInfoProvider(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : VisaContractInfoProvider {
|
||||
|
||||
override suspend fun getBalancesAndLimits(walletAddress: String): BalancesAndLimits = withContext(dispatchers.io) {
|
||||
override suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits {
|
||||
return withContext(dispatchers.io) {
|
||||
val tangemBridgeProcessor = TangemBridgeProcessor.load(
|
||||
/* contractAddress = */ bridgeProcessorAddress,
|
||||
/* web3j = */ web3j,
|
||||
|
|
@ -38,6 +39,7 @@ internal class DefaultVisaContractInfoProvider(
|
|||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPaymentAccount(
|
||||
walletAddress: String,
|
||||
|
|
@ -62,12 +64,12 @@ internal class DefaultVisaContractInfoProvider(
|
|||
private suspend fun fetchBalancesAndLimits(
|
||||
paymentAccount: TangemPaymentAccount,
|
||||
paymentToken: PaymentTokenInfo,
|
||||
): BalancesAndLimits = parZip(
|
||||
): VisaBalancesAndLimits = parZip(
|
||||
dispatchers.io,
|
||||
{ fetchBalances(paymentAccount, paymentToken) },
|
||||
{ fetchLimits(paymentAccount, paymentToken) },
|
||||
{ balances, (oldLimit, newLimit, changeDate) ->
|
||||
BalancesAndLimits(balances, oldLimit, newLimit, changeDate)
|
||||
VisaBalancesAndLimits(balances, oldLimit, newLimit, changeDate)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.lib.visa
|
|||
|
||||
import com.ihsanbal.logging.Level
|
||||
import com.ihsanbal.logging.LoggingInterceptor
|
||||
import com.tangem.lib.visa.model.BalancesAndLimits
|
||||
import com.tangem.lib.visa.model.VisaBalancesAndLimits
|
||||
import com.tangem.lib.visa.utils.VisaConfig
|
||||
import com.tangem.lib.visa.utils.VisaConfig.NETWORK_LOGS_TAG
|
||||
import com.tangem.lib.visa.utils.toHexString
|
||||
|
|
@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
interface VisaContractInfoProvider {
|
||||
|
||||
suspend fun getBalancesAndLimits(walletAddress: String): BalancesAndLimits
|
||||
suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits
|
||||
|
||||
class Builder(
|
||||
private val isNetworkLoggingEnabled: Boolean,
|
||||
|
|
|
|||
16
libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt
Normal file
16
libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.lib.visa.api
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.lib.visa.model.VisaTxHistoryResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface VisaApi {
|
||||
|
||||
@GET("transaction")
|
||||
suspend fun getTxHistory(
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
@Query("limit") limit: Int,
|
||||
@Query("offset") offset: Int,
|
||||
): ApiResponse<VisaTxHistoryResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.lib.visa.api
|
||||
|
||||
import android.util.Log
|
||||
import com.ihsanbal.logging.Level
|
||||
import com.ihsanbal.logging.LoggingInterceptor
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.lib.visa.utils.VisaConfig
|
||||
import com.tangem.lib.visa.utils.VisaConfig.NETWORK_LOGS_TAG
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class VisaApiBuilder(
|
||||
private val useDevApi: Boolean,
|
||||
private val isNetworkLoggingEnabled: Boolean,
|
||||
private val moshi: Moshi,
|
||||
private val networkTimeoutSeconds: Long = VisaConfig.NETWORK_TIMEOUT_SECONDS,
|
||||
) {
|
||||
|
||||
fun build(): VisaApi {
|
||||
val okHttpClient = createOkHttpClient()
|
||||
val retrofit = createRetrofit(okHttpClient)
|
||||
return retrofit.create(VisaApi::class.java)
|
||||
}
|
||||
|
||||
private fun createOkHttpClient(): OkHttpClient {
|
||||
val builder = OkHttpClient.Builder().apply {
|
||||
connectTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
readTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
writeTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
|
||||
if (isNetworkLoggingEnabled) {
|
||||
addInterceptor(createNetworkLoggingInterceptor())
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun createRetrofit(okHttpClient: OkHttpClient): Retrofit {
|
||||
val baseUrl = if (useDevApi) VisaConfig.VISA_API_DEV_URL else VisaConfig.VISA_API_PROD_URL
|
||||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
|
||||
.baseUrl(baseUrl)
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNetworkLoggingInterceptor(): Interceptor {
|
||||
return LoggingInterceptor.Builder()
|
||||
.setLevel(Level.BODY)
|
||||
.log(Log.VERBOSE)
|
||||
.tag(NETWORK_LOGS_TAG)
|
||||
.build()
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import org.joda.time.Instant
|
|||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
data class BalancesAndLimits(
|
||||
data class VisaBalancesAndLimits(
|
||||
val balances: Balances,
|
||||
val oldLimits: Limits,
|
||||
val newLimits: Limits,
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.lib.visa.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class VisaTxHistoryResponse(
|
||||
@Json(name = "card_wallet_address")
|
||||
val cardWalletAddress: String,
|
||||
@Json(name = "transactions")
|
||||
val transactions: List<Transaction>,
|
||||
) {
|
||||
|
||||
data class Transaction(
|
||||
@Json(name = "auth_code")
|
||||
val authCode: String?,
|
||||
@Json(name = "billing_amount")
|
||||
val billingAmount: BigDecimal,
|
||||
@Json(name = "billing_currency_code")
|
||||
val billingCurrencyCode: Int,
|
||||
@Json(name = "blockchain_amount")
|
||||
val blockchainAmount: BigDecimal,
|
||||
@Json(name = "blockchain_coin_name")
|
||||
val blockchainCoinName: String,
|
||||
@Json(name = "blockchain_fee")
|
||||
val blockchainFee: BigDecimal,
|
||||
// @Json(name = "local_dt")
|
||||
// val localDate: DateTime?,
|
||||
@Json(name = "merchant_category_code")
|
||||
val merchantCategoryCode: String?,
|
||||
@Json(name = "merchant_city")
|
||||
val merchantCity: String?,
|
||||
@Json(name = "merchant_country_code")
|
||||
val merchantCountryCode: String?,
|
||||
@Json(name = "merchant_name")
|
||||
val merchantName: String?,
|
||||
@Json(name = "requests")
|
||||
val requests: List<Request>,
|
||||
@Json(name = "rrn")
|
||||
val rrn: String?,
|
||||
@Json(name = "transaction_amount")
|
||||
val transactionAmount: BigDecimal,
|
||||
@Json(name = "transaction_currency_code")
|
||||
val transactionCurrencyCode: Int,
|
||||
@Json(name = "transaction_dt")
|
||||
val transactionDt: DateTime,
|
||||
@Json(name = "transaction_id")
|
||||
val transactionId: Long,
|
||||
@Json(name = "transaction_status")
|
||||
val transactionStatus: String,
|
||||
@Json(name = "transaction_type")
|
||||
val transactionType: String,
|
||||
) {
|
||||
|
||||
data class Request(
|
||||
@Json(name = "billing_amount")
|
||||
val billingAmount: BigDecimal,
|
||||
@Json(name = "billing_currency_code")
|
||||
val billingCurrencyCode: Int,
|
||||
@Json(name = "blockchain_amount")
|
||||
val blockchainAmount: BigDecimal,
|
||||
@Json(name = "blockchain_fee")
|
||||
val blockchainFee: BigDecimal,
|
||||
@Json(name = "error_code")
|
||||
val errorCode: Int,
|
||||
@Json(name = "request_dt")
|
||||
val requestDt: DateTime,
|
||||
@Json(name = "request_status")
|
||||
val requestStatus: String,
|
||||
@Json(name = "request_type")
|
||||
val requestType: String,
|
||||
@Json(name = "transaction_amount")
|
||||
val transactionAmount: BigDecimal,
|
||||
@Json(name = "transaction_currency_code")
|
||||
val transactionCurrencyCode: Int,
|
||||
@Json(name = "transaction_request_id")
|
||||
val transactionRequestId: Long,
|
||||
@Json(name = "tx_hash")
|
||||
val txHash: String?,
|
||||
@Json(name = "tx_status")
|
||||
val txStatus: String?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,9 @@ internal object VisaConfig {
|
|||
const val GAS_LIMIT = 500_000_000L
|
||||
const val PRIVATE_KEY_LENGTH = 32
|
||||
|
||||
const val VISA_API_PROD_URL = "https://payapi.tangem-tech.com/api/v1/"
|
||||
const val VISA_API_DEV_URL = "[REDACTED_ENV_URL]"
|
||||
|
||||
const val NETWORK_TIMEOUT_SECONDS = 65L
|
||||
const val NETWORK_LOGS_TAG = "VisaNetworkLogs"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue