diff --git a/example/src/main/java/com/basistheory/android/example/view/dual_write/DualWriteFragment.kt b/example/src/main/java/com/basistheory/android/example/view/dual_write/DualWriteFragment.kt new file mode 100644 index 00000000..5e9ef82b --- /dev/null +++ b/example/src/main/java/com/basistheory/android/example/view/dual_write/DualWriteFragment.kt @@ -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) + } + } +} diff --git a/example/src/main/java/com/basistheory/android/example/viewmodel/ApiViewModel.kt b/example/src/main/java/com/basistheory/android/example/viewmodel/ApiViewModel.kt index 7a087dcb..1e6718ff 100644 --- a/example/src/main/java/com/basistheory/android/example/viewmodel/ApiViewModel.kt +++ b/example/src/main/java/com/basistheory/android/example/viewmodel/ApiViewModel.kt @@ -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(null) val errorMessage: LiveData get() = _errorMessage @@ -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, + body: Any + ): LiveData = performRequest(HttpMethod.POST, url, headers, body) + + fun get( + url: String, + headers: Map + ): LiveData = performRequest(HttpMethod.GET, url, headers, null) + + fun put( + url: String, + headers: Map, + body: Any + ): LiveData = performRequest(HttpMethod.PUT, url, headers, body) + + fun patch( + url: String, + headers: Map, + body: Any + ): LiveData = performRequest(HttpMethod.PATCH, url, headers, body) + + fun delete( + url: String, + headers: Map + ): LiveData = performRequest(HttpMethod.DELETE, url, headers, null) + + + private fun performRequest( + method: HttpMethod, + url: String, + headers: Map, + body: Any? + ): LiveData = 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() + .resources + .getString(R.string.tokenize_error, it) + } + ) + } + + } + + fun tokenize(payload: Any): LiveData = liveData { _errorMessage.value = null _result.value = null diff --git a/example/src/main/res/layout/fragment_dual_write.xml b/example/src/main/res/layout/fragment_dual_write.xml new file mode 100644 index 00000000..af675805 --- /dev/null +++ b/example/src/main/res/layout/fragment_dual_write.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + +