Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds bt http client #74

Merged
merged 2 commits into from
Jun 15, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.basistheory.android.example.view.dual_write

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.basistheory.android.example.databinding.FragmentDualWriteBinding
import com.basistheory.android.example.viewmodel.CardFragmentViewModel
import com.basistheory.android.service.HttpMethod

class DualWriteFragment : Fragment() {
private val binding: FragmentDualWriteBinding by lazy {
FragmentDualWriteBinding.inflate(layoutInflater)
}

private val viewModel: CardFragmentViewModel by viewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
binding.lifecycleOwner = this
binding.viewModel = viewModel

binding.cvc.cardNumberElement = binding.cardNumber

binding.postButton.setOnClickListener { createPaymentMethod() }
binding.autofillButton.setOnClickListener { autofill() }

setValidationListeners()

return binding.root
}


private fun autofill() {
binding.cardNumber.setText("4242424242424242")
binding.expirationDate.setText("12/25")
binding.cvc.setText("123")
}

private fun createPaymentMethod() = viewModel.client.post(
"https://api.stripe.com/v1/payment_methods", headers = mapOf(
"Authorization" to "Bearer {{ STRIPE'S API KEY}}",
"Content-Type" to "application/x-www-form-urlencoded"
), object {
val type = "card"
val billing_details = object {
val name = "Peter Panda"
}
val card = object {
val number = binding.cardNumber
val exp_month = binding.expirationDate.month()
val exp_year = binding.expirationDate.year()
val cvc = binding.cvc
}
}
).observe(viewLifecycleOwner) {}

/**
* demonstrates how an application could potentially wire up custom validation behaviors
*/
private fun setValidationListeners() {
binding.cardNumber.addChangeEventListener {
viewModel.cardNumber.observe(it)
}

binding.expirationDate.addChangeEventListener {
viewModel.cardExpiration.observe(it)
}

binding.cvc.addChangeEventListener {
viewModel.cardCvc.observe(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import com.basistheory.android.example.BuildConfig
import com.basistheory.android.example.R
import com.basistheory.android.example.util.prettyPrintJson
import com.basistheory.android.service.BasisTheoryElements
import com.basistheory.android.service.HttpMethod
import com.basistheory.android.service.ProxyRequest

open class ApiViewModel(application: Application): AndroidViewModel(application) {
open class ApiViewModel(application: Application) : AndroidViewModel(application) {
private val _errorMessage = MutableLiveData<String?>(null)
val errorMessage: LiveData<String?>
get() = _errorMessage
Expand All @@ -30,6 +31,74 @@ open class ApiViewModel(application: Application): AndroidViewModel(application)
.apiKey(BuildConfig.BASIS_THEORY_API_KEY)
.build()


val client = Client()

inner class Client {
fun post(
url: String,
headers: Map<String, String>,
body: Any
): LiveData<Any> = performRequest(HttpMethod.POST, url, headers, body)

fun get(
url: String,
headers: Map<String, String>
): LiveData<Any> = performRequest(HttpMethod.GET, url, headers, null)

fun put(
url: String,
headers: Map<String, String>,
body: Any
): LiveData<Any> = performRequest(HttpMethod.PUT, url, headers, body)

fun patch(
url: String,
headers: Map<String, String>,
body: Any
): LiveData<Any> = performRequest(HttpMethod.PATCH, url, headers, body)

fun delete(
url: String,
headers: Map<String, String>
): LiveData<Any> = performRequest(HttpMethod.DELETE, url, headers, null)


private fun performRequest(
method: HttpMethod,
url: String,
headers: Map<String, String>,
body: Any?
): LiveData<Any> = liveData {
_errorMessage.value = null
_result.value = null

runCatching {
when (method) {
HttpMethod.GET -> bt.client.get(url, headers)
HttpMethod.POST -> bt.client.post(url, body!!, headers)
HttpMethod.PUT -> bt.client.put(url, body!!, headers)
HttpMethod.PATCH -> bt.client.patch(url, body!!, headers)
HttpMethod.DELETE -> bt.client.delete(url, headers)
}
}.fold(
onSuccess = {
if (it != null) {
_result.value = it.prettyPrintJson()
emit(it)
}
},
onFailure = {
_errorMessage.value = getApplication<Application>()
.resources
.getString(R.string.tokenize_error, it)
}
)
}

}


fun tokenize(payload: Any): LiveData<Any> = liveData {
_errorMessage.value = null
_result.value = null
Expand Down
109 changes: 109 additions & 0 deletions example/src/main/res/layout/fragment_dual_write.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>

<import type="android.view.View" />

<variable
name="viewModel"
type="com.basistheory.android.example.viewmodel.CardFragmentViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.basistheory.android.example.view.dual_write.DualWriteFragment">

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical">

<com.basistheory.android.view.CardNumberElement
android:id="@+id/card_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/rounded_edit_text"
android:padding="5dp"
android:hint="@string/card_number"
android:textColor="@{ viewModel.cardNumber.isInvalid ? @color/red : @color/gray_800 }" />

<com.basistheory.android.view.CardExpirationDateElement
android:id="@+id/expiration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="@drawable/rounded_edit_text"
android:padding="5dp"
android:hint="@string/expiration_date"
android:textColor="@{ viewModel.cardExpiration.isInvalid ? @color/red : @color/gray_800 }" />

<com.basistheory.android.view.CardVerificationCodeElement
android:id="@+id/cvc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="@drawable/rounded_edit_text"
android:padding="5dp"
android:hint="@string/cvc"
android:textColor="@{ viewModel.cardCvc.isInvalid ? @color/red : @color/gray_800 }" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">

<Button
android:id="@+id/post_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:backgroundTint="@color/primary"
android:enabled="@{ viewModel.canSubmit }"
android:text="@string/post" />

<Button
android:id="@+id/autofill_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:backgroundTint="@color/primary"
android:text="@string/autofill" />

</LinearLayout>

<TextView
android:id="@+id/error_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:fontFamily="monospace"
android:text="@{ viewModel.errorMessage }"
android:textColor="@color/red"
android:visibility="@{ viewModel.errorMessage != null ? View.VISIBLE : View.GONE }" />

<TextView
android:id="@+id/result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:fontFamily="monospace"
android:text="@{ viewModel.result }"
android:visibility="@{ viewModel.result != null ? View.VISIBLE : View.GONE }" />

</LinearLayout>

</ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>
4 changes: 4 additions & 0 deletions example/src/main/res/menu/navigation_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,9 @@
<item
android:id="@+id/nav_raw_proxy_response"
android:title="@string/title_raw_proxy_response" />

<item
android:id="@+id/nav_dual_write"
android:title="@string/title_dual_write" />
</group>
</menu>
6 changes: 6 additions & 0 deletions example/src/main/res/navigation/mobile_navigation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@
android:label="@string/title_raw_proxy_response"
tools:layout="@layout/fragment_raw_proxy_response" />

<fragment
android:id="@+id/nav_dual_write"
android:name="com.basistheory.android.example.view.dual_write.DualWriteFragment"
android:label="@string/title_dual_write"
tools:layout="@layout/fragment_dual_write" />

</navigation>
9 changes: 9 additions & 0 deletions example/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<string name="title_reveal">Reveal Data</string>
<string name="title_styling">Custom Styling</string>
<string name="title_raw_proxy_response">Raw Proxy Response</string>
<string name="title_dual_write">HTTP client</string>

<string name="autofill">Autofill</string>
<string name="tokenize">Tokenize</string>
Expand All @@ -31,4 +32,12 @@
<string name="password">Password</string>
<string name="phone_number">Phone Number</string>
<string name="pin">PIN</string>



<string name="post">POST</string>
<string name="get">GET</string>
<string name="delete">DELETE</string>
<string name="patch">PATCH</string>
<string name="put">PUT</string>
</resources>
1 change: 1 addition & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ dependencies {
testImplementation 'io.strikt:strikt-core:0.34.1'
testImplementation 'com.github.javafaker:javafaker:1.0.2'
testImplementation 'io.mockk:mockk:1.13.5'
testImplementation("com.squareup.okhttp3:mockwebserver:4.11.0")
}

configurations.all {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.basistheory.android.service

import HttpClient
import com.basistheory.CreateSessionResponse
import com.basistheory.CreateTokenRequest
import com.basistheory.CreateTokenResponse
import com.basistheory.Token
import com.basistheory.android.model.ElementValueReference
import com.basistheory.android.util.isPrimitiveType
import com.basistheory.android.util.toMap
import com.basistheory.android.util.transformResponseToValueReferences
import com.basistheory.android.util.*
import com.basistheory.android.util.getElementsValues
import com.basistheory.android.util.replaceElementRefs
import com.basistheory.android.util.tryGetTextToTokenize
import com.basistheory.android.view.TextElement
Expand All @@ -20,16 +20,13 @@ class BasisTheoryElements internal constructor(
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
val proxy: ProxyApi = apiClientProvider.getProxyApi(dispatcher)
val client = HttpClient(dispatcher)

@JvmOverloads
suspend fun tokenize(body: Any, apiKeyOverride: String? = null): Any =
withContext(dispatcher) {
val tokenizeApiClient = apiClientProvider.getTokenizeApi(apiKeyOverride)
val request =
if (body::class.java.isPrimitiveType()) body
else if (body is TextElement) body.getTransformedText()
else if (body is ElementValueReference) body.getValue()
else replaceElementRefs(body)
val request = getElementsValues(body)

tokenizeApiClient.tokenize(request)
}
Expand Down
Loading