Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
<!-- Please describe in detail how teammates can test your changes. -->
## Testing required outside of automated testing?

- [ ] Not Applicable
- [x] Not Applicable

<!-- Provide Screenshots when applicable -->
### Screenshots (if appropriate):

- [ ] Not Applicable
- [x] Not Applicable

<!-- Describe Rollback or Rollforward Procedure -->
## Rollback / Rollforward Procedure

- [ ] Roll Forward
- [x] Roll Forward
- [ ] Roll Back

## Reviewer Checklist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import org.threeten.bp.Instant
import org.threeten.bp.temporal.ChronoUnit

fun tokenExpirationTimestamp() = Instant.now()
.plus(5, ChronoUnit.MINUTES)
.plus(30, ChronoUnit.MINUTES)
.toString()
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.basistheory.elements.example.view.card

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -11,7 +10,6 @@ import com.basistheory.elements.constants.CoBadgedSupport
import com.basistheory.elements.example.databinding.FragmentCardBinding
import com.basistheory.elements.example.util.tokenExpirationTimestamp
import com.basistheory.elements.example.viewmodel.CardFragmentViewModel
import com.basistheory.elements.service.BasisTheoryElements
import com.basistheory.elements.view.CardBrandSelectorOptions

class CardFragment : Fragment() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.basistheory.elements.service

import com.basistheory.BasisTheoryApiClient
import com.basistheory.core.Environment
import com.basistheory.resources.enrichments.EnrichmentsClient
import com.basistheory.resources.sessions.SessionsClient
import com.basistheory.resources.tokenintents.TokenIntentsClient
Expand All @@ -10,7 +11,8 @@ import kotlinx.coroutines.Dispatchers

internal class ApiClientProvider(
private val apiUrl: String = "https://api.basistheory.com",
private val defaultApiKey: String? = null
private val defaultApiKey: String? = null,
private val environment: Environment? = Environment.DEFAULT
) {
fun getTokensApi(apiKeyOverride: String? = null): TokensClient =
getApiClient(apiKeyOverride).tokens()
Expand All @@ -34,10 +36,15 @@ internal class ApiClientProvider(
val apiKey = apiKeyOverride ?: defaultApiKey
requireNotNull(apiKey)

return BasisTheoryApiClient.builder()
val apiClient = BasisTheoryApiClient.builder()
.apiKey(apiKey)
.url(apiUrl)
.httpClient(createHttpClientWithDeviceInfo())
.build()
.environment(environment)
.httpClient(createHttpClientWithDeviceInfo());

if (apiUrl != "https://api.basistheory.com" && environment === Environment.DEFAULT) {
apiClient.url(apiUrl)
}

return apiClient.build()
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.basistheory.elements.service

import com.basistheory.core.Environment
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

class BasisTheoryElementsBuilder {
private var _apiKey: String? = null
private var _apiUrl: String = "https://api.basistheory.com"
private var _environment: Environment = Environment.DEFAULT
private var _dispatcher: CoroutineDispatcher = Dispatchers.IO

fun apiKey(value: String): BasisTheoryElementsBuilder {
Expand All @@ -18,14 +20,19 @@ class BasisTheoryElementsBuilder {
return this
}

fun environment(value: Environment): BasisTheoryElementsBuilder {
_environment = value
return this
}

fun dispatcher(value: CoroutineDispatcher): BasisTheoryElementsBuilder {
_dispatcher = value
return this
}

fun build(): BasisTheoryElements =
BasisTheoryElements(
ApiClientProvider(_apiUrl, _apiKey),
ApiClientProvider(_apiUrl, _apiKey, _environment),
_dispatcher
)
}
11 changes: 7 additions & 4 deletions lib/src/main/java/com/basistheory/elements/service/ProxyApi.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.basistheory.elements.service

import com.basistheory.core.Environment
import com.basistheory.elements.model.ElementValueReference
import com.basistheory.elements.model.exceptions.ApiException
import com.basistheory.elements.util.getApiUrl
import com.basistheory.elements.util.getEncodedDeviceInfo
import com.basistheory.elements.util.isPrimitiveType
import com.basistheory.elements.util.replaceElementRefs
Expand All @@ -19,7 +20,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody


interface Proxy {

suspend fun get(proxyRequest: ProxyRequest, apiKeyOverride: String? = null): Any?
Expand Down Expand Up @@ -54,7 +54,8 @@ class ProxyApi(
val dispatcher: CoroutineDispatcher = Dispatchers.IO,
val apiBaseUrl: String = "https://api.basistheory.com",
val apiKey: String,
val httpClient: OkHttpClient = OkHttpClient()
val httpClient: OkHttpClient = OkHttpClient(),
val environment: Environment = Environment.DEFAULT
) : Proxy {

override suspend fun get(proxyRequest: ProxyRequest, apiKeyOverride: String?): Any? =
Expand Down Expand Up @@ -94,7 +95,8 @@ class ProxyApi(
}
}

val urlBuilder = (apiBaseUrl + "/proxy" + (proxyRequest.path.orEmpty()))
val finalApiBaseUrl = if (apiBaseUrl !== "https://api.basistheory.com") apiBaseUrl else environment.getApiUrl()
val urlBuilder = (finalApiBaseUrl + "/proxy" + (proxyRequest.path.orEmpty()))
.toHttpUrlOrNull()?.newBuilder()
?: throw IllegalArgumentException("Invalid URL")
proxyRequest.queryParams?.toPairs()?.forEach { (key, value) ->
Expand All @@ -112,6 +114,7 @@ class ProxyApi(
val requestBody = when {
method.equals("GET", ignoreCase = true) ||
method.equals("DELETE", ignoreCase = true) -> null

isTextPlain -> processedBody?.toString().orEmpty()
.toRequestBody("text/plain; charset=utf-8".toMediaTypeOrNull())

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.basistheory.elements.util

import com.basistheory.core.Environment

const val ApiUrl: String = "https://api.basistheory.com"
const val ApiTestUrl: String = "https://api.test.basistheory.com"

fun Environment.getApiUrl(): String =
when (this) {
Environment.US -> ApiUrl
Environment.EU -> ApiUrl
Environment.TEST -> ApiTestUrl
Environment.DEFAULT -> ApiUrl
else -> ApiUrl
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ import com.basistheory.elements.model.BinDetails
import com.basistheory.elements.model.BinRange
import com.basistheory.elements.model.CardMetadata
import com.basistheory.elements.model.InputType
import com.basistheory.elements.service.ApiClientProvider
import com.basistheory.elements.service.CardBrandEnricher
import com.basistheory.elements.util.BinDetailsCache
import com.basistheory.elements.view.mask.ElementMask
import com.basistheory.elements.view.transform.RegexReplaceElementTransform
import com.basistheory.elements.view.validation.LuhnValidator
import com.basistheory.resources.enrichments.requests.EnrichmentsGetCardDetailsRequest
import com.basistheory.types.CardDetailsResponse
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlin.coroutines.cancellation.CancellationException

class CardNumberElement @JvmOverloads constructor(
context: Context,
Expand Down Expand Up @@ -141,6 +139,8 @@ class CardNumberElement @JvmOverloads constructor(
BinDetailsCache.put(bin, binDetails)
updateBinDetails(binDetails)
}
} catch (e: CancellationException) {
Log.i("CardNumberElement", "Fetch BIN details job cancelled")
} catch (e: Exception) {
Log.e("CardNumberElement", "Error fetching BIN details", e)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.basistheory.elements.service

import com.basistheory.core.Environment
import com.basistheory.elements.model.ElementValueReference
import com.basistheory.elements.util.getEncodedDeviceInfo
import io.mockk.CapturingSlot
Expand Down Expand Up @@ -76,9 +77,9 @@ class ProxyApiTests {
fun setup() {
proxyApi = ProxyApi(
dispatcher = Dispatchers.IO,
apiBaseUrl = "https://api.basistheory.com",
apiKey = "124",
httpClient = mockHttpClient
httpClient = mockHttpClient,
environment = Environment.US
)
proxyRequest = ProxyRequest()

Expand Down Expand Up @@ -140,6 +141,43 @@ class ProxyApiTests {
expectThat(result).isA<ElementValueReference>()
}

@Test
fun `should use uat url when environment is test`() {
proxyApi = ProxyApi(
dispatcher = Dispatchers.IO,
apiKey = "124",
httpClient = mockHttpClient,
environment = Environment.TEST
)

val queryParamValue = UUID.randomUUID().toString()
proxyRequest = proxyRequest.apply {
path = "/payment"
headers = mapOf(
"BT-PROXY-URL" to "https://echo.basistheory.com/post",
"Content-Type" to "text/plain"
)
queryParams = mapOf("param" to queryParamValue)
body = "Hello World"
}

val requestSlot = setupMocks("\"Hello World\"")

val result = runBlocking {
proxyApi.post(proxyRequest)
}

verify(exactly = 1) { mockHttpClient.newCall(any()) }
verify(exactly = 1) { mockCall.execute() }

expectThat(requestSlot.captured) {
get { url.toString() }
.isEqualTo("https://api.test.basistheory.com/proxy/payment?param=${queryParamValue}")
}

expectThat(result).isA<ElementValueReference>()
}

@Test
fun `should transform complex proxy response to element value references`() {
val responseJson = """
Expand Down
Loading