From 8b77ca2487a01f786cf845e5f644b98460036708 Mon Sep 17 00:00:00 2001
From: Chen Cen <79880926+ccen-stripe@users.noreply.github.com>
Date: Thu, 26 Aug 2021 11:01:38 -0700
Subject: [PATCH] [PaymentLauncher] Replace `Stripe` with `PaymentLauncher` in
example app (#4125)
* change
* change
---
example/AndroidManifest.xml | 1 +
example/build.gradle | 15 ++
example/res/values/strings.xml | 1 +
.../activity/ComposeExampleActivity.kt | 181 ++++++++++++++++++
.../activity/ConfirmSepaDebitActivity.kt | 19 +-
.../example/activity/LauncherActivity.kt | 4 +
.../example/activity/StripeIntentActivity.kt | 114 +++++------
.../example/activity/UpiPaymentActivity.kt | 18 +-
.../example/module/StripeIntentViewModel.kt | 6 +-
9 files changed, 270 insertions(+), 89 deletions(-)
create mode 100644 example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt
diff --git a/example/AndroidManifest.xml b/example/AndroidManifest.xml
index ca1b3c486a0..031d38f928a 100644
--- a/example/AndroidManifest.xml
+++ b/example/AndroidManifest.xml
@@ -65,6 +65,7 @@
+
diff --git a/example/build.gradle b/example/build.gradle
index 44a1f70862e..7b04b931f95 100644
--- a/example/build.gradle
+++ b/example/build.gradle
@@ -57,6 +57,17 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"
+
+ // Jetpack Compose
+ // Integration with activities
+ implementation 'androidx.activity:activity-compose:1.3.1'
+ // Compose Material Design
+ implementation "androidx.compose.material:material:$composeVersion"
+ // Integration with observables
+ implementation "androidx.compose.runtime:runtime-livedata:$composeVersion"
+ // end of Jetpack Compose
+
+
ktlint "com.pinterest:ktlint:$ktlintVersion"
testImplementation "androidx.test:core:$androidTestVersion"
@@ -158,6 +169,10 @@ android {
buildFeatures {
viewBinding true
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion "$composeVersion"
}
}
diff --git a/example/res/values/strings.xml b/example/res/values/strings.xml
index b7acf93b3fa..f369174df00 100644
--- a/example/res/values/strings.xml
+++ b/example/res/values/strings.xml
@@ -91,6 +91,7 @@
Confirm with NetBanking
Connect Example
Simple PaymentMethod Confirmation Example
+ Compose Example
Virtual Payment Address (VPA)
diff --git a/example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt b/example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt
new file mode 100644
index 00000000000..bb040b63e99
--- /dev/null
+++ b/example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt
@@ -0,0 +1,181 @@
+package com.stripe.example.activity
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.Divider
+import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.stripe.android.model.Address
+import com.stripe.android.model.ConfirmPaymentIntentParams
+import com.stripe.android.model.PaymentMethodCreateParams
+import com.stripe.android.payments.paymentlauncher.PaymentLauncher
+import com.stripe.android.payments.paymentlauncher.PaymentResult
+import com.stripe.example.R
+import com.stripe.example.Settings
+import com.stripe.example.module.StripeIntentViewModel
+
+/**
+ * An Activity to demonstrate [PaymentLauncher] with Jetpack Compose.
+ */
+class ComposeExampleActivity : AppCompatActivity() {
+ private val viewModel: StripeIntentViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ ComposeScreen()
+ }
+ }
+
+ @Composable
+ fun ComposeScreen() {
+ val inProgress by viewModel.inProgress.observeAsState(false)
+ val status by viewModel.status.observeAsState("")
+
+ createPaymentLauncher().let { paymentLauncher ->
+ Column(modifier = Modifier.padding(horizontal = 10.dp)) {
+ if (inProgress) {
+ LinearProgressIndicator(
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ Text(
+ stringResource(R.string.payment_auth_intro),
+ modifier = Modifier.padding(vertical = 5.dp),
+ )
+ ConfirmButton(
+ params = confirmParams3ds1,
+ buttonName = R.string.confirm_with_3ds1_button,
+ paymentLauncher = paymentLauncher,
+ inProgress = inProgress
+ )
+ ConfirmButton(
+ params = confirmParams3ds2,
+ buttonName = R.string.confirm_with_3ds2_button,
+ paymentLauncher = paymentLauncher,
+ inProgress = inProgress
+ )
+ Divider(modifier = Modifier.padding(vertical = 5.dp))
+ Text(text = status)
+ }
+ }
+ }
+
+ /**
+ * Create [PaymentLauncher] in a [Composable]
+ */
+ @Composable
+ fun createPaymentLauncher(): PaymentLauncher {
+ val settings = Settings(LocalContext.current)
+ return PaymentLauncher.createForCompose(
+ publishableKey = settings.publishableKey,
+ stripeAccountId = settings.stripeAccountId
+ ) {
+ when (it) {
+ is PaymentResult.Completed -> {
+ viewModel.status.value += "\n\nPaymentIntent confirmation succeeded\n\n"
+ viewModel.inProgress.value = false
+ }
+ is PaymentResult.Canceled -> {
+ viewModel.status.value += "\n\nPaymentIntent confirmation cancelled\n\n"
+ viewModel.inProgress.value = false
+ }
+ is PaymentResult.Failed -> {
+ viewModel.status.value += "\n\nPaymentIntent confirmation failed with " +
+ "throwable ${it.throwable} \n\n"
+ viewModel.inProgress.value = false
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun ConfirmButton(
+ params: PaymentMethodCreateParams,
+ @StringRes buttonName: Int,
+ paymentLauncher: PaymentLauncher,
+ inProgress: Boolean
+ ) {
+ Button(
+ onClick = { createAndConfirmPaymentIntent(params, paymentLauncher) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ enabled = !inProgress
+ ) {
+ Text(stringResource(buttonName))
+ }
+ }
+
+ private fun createAndConfirmPaymentIntent(
+ params: PaymentMethodCreateParams,
+ paymentLauncher: PaymentLauncher,
+ ) {
+ viewModel.createPaymentIntent("us").observe(
+ this
+ ) {
+ it.onSuccess { responseData ->
+ val confirmPaymentIntentParams =
+ ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams(
+ paymentMethodCreateParams = params,
+ clientSecret = responseData.getString("secret"),
+ shipping = SHIPPING
+ )
+ paymentLauncher.confirm(confirmPaymentIntentParams)
+ }
+ }
+ }
+
+ private companion object {
+ /**
+ * See https://stripe.com/docs/payments/3d-secure#three-ds-cards for more options.
+ */
+ private val confirmParams3ds2 =
+ PaymentMethodCreateParams.create(
+ PaymentMethodCreateParams.Card.Builder()
+ .setNumber("4000000000003238")
+ .setExpiryMonth(1)
+ .setExpiryYear(2025)
+ .setCvc("123")
+ .build()
+ )
+
+ private val confirmParams3ds1 =
+ PaymentMethodCreateParams.create(
+ PaymentMethodCreateParams.Card.Builder()
+ .setNumber("4000000000003063")
+ .setExpiryMonth(1)
+ .setExpiryYear(2025)
+ .setCvc("123")
+ .build()
+ )
+
+ private val SHIPPING = ConfirmPaymentIntentParams.Shipping(
+ address = Address.Builder()
+ .setCity("San Francisco")
+ .setCountry("US")
+ .setLine1("123 Market St")
+ .setLine2("#345")
+ .setPostalCode("94107")
+ .setState("CA")
+ .build(),
+ name = "Jenny Rosen",
+ carrier = "Fedex",
+ trackingNumber = "12345"
+ )
+ }
+}
diff --git a/example/src/main/java/com/stripe/example/activity/ConfirmSepaDebitActivity.kt b/example/src/main/java/com/stripe/example/activity/ConfirmSepaDebitActivity.kt
index 77cdfb8aafe..cb4adc4110e 100644
--- a/example/src/main/java/com/stripe/example/activity/ConfirmSepaDebitActivity.kt
+++ b/example/src/main/java/com/stripe/example/activity/ConfirmSepaDebitActivity.kt
@@ -3,11 +3,11 @@ package com.stripe.example.activity
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
-import com.stripe.android.PaymentIntentResult
import com.stripe.android.model.Address
import com.stripe.android.model.MandateDataParams
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
+import com.stripe.android.payments.paymentlauncher.PaymentResult
import com.stripe.example.R
import com.stripe.example.databinding.CreateSepaDebitActivityBinding
@@ -54,14 +54,19 @@ class ConfirmSepaDebitActivity : StripeIntentActivity() {
viewBinding.confirmButton.isEnabled = enabled
}
- override fun onConfirmSuccess(result: PaymentIntentResult) {
- super.onConfirmSuccess(result)
- snackbarController.show("Status after confirmation: ${result.intent.status}")
+ override fun onConfirmSuccess() {
+ super.onConfirmSuccess()
+ snackbarController.show("Confirmation succeeded.")
}
- override fun onConfirmError(throwable: Throwable) {
- super.onConfirmError(throwable)
- snackbarController.show("Error during confirmation: ${throwable.message}")
+ override fun onConfirmCanceled() {
+ super.onConfirmCanceled()
+ snackbarController.show("Confirmation canceled.")
+ }
+
+ override fun onConfirmError(failedResult: PaymentResult.Failed) {
+ super.onConfirmError(failedResult)
+ snackbarController.show("Error during confirmation: ${failedResult.throwable.message}")
}
private fun createPaymentMethodParams(iban: String): PaymentMethodCreateParams {
diff --git a/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt b/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt
index 5a7ff8905b8..18003b41ebb 100644
--- a/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt
+++ b/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt
@@ -119,6 +119,10 @@ class LauncherActivity : AppCompatActivity() {
Item(
activity.getString(R.string.connect_example),
ConnectExampleActivity::class.java
+ ),
+ Item(
+ activity.getString(R.string.compose_example),
+ ComposeExampleActivity::class.java
)
)
diff --git a/example/src/main/java/com/stripe/example/activity/StripeIntentActivity.kt b/example/src/main/java/com/stripe/example/activity/StripeIntentActivity.kt
index abc1e301afa..be2d19ec38d 100644
--- a/example/src/main/java/com/stripe/example/activity/StripeIntentActivity.kt
+++ b/example/src/main/java/com/stripe/example/activity/StripeIntentActivity.kt
@@ -1,24 +1,17 @@
package com.stripe.example.activity
-import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
-import com.stripe.android.PaymentIntentResult
-import com.stripe.android.SetupIntentResult
-import com.stripe.android.Stripe
-import com.stripe.android.getPaymentIntentResult
-import com.stripe.android.getSetupIntentResult
+import com.stripe.android.PaymentConfiguration
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.MandateDataParams
import com.stripe.android.model.PaymentMethodCreateParams
-import com.stripe.example.R
+import com.stripe.android.payments.paymentlauncher.PaymentLauncher
+import com.stripe.android.payments.paymentlauncher.PaymentResult
import com.stripe.example.Settings
-import com.stripe.example.StripeFactory
import com.stripe.example.module.StripeIntentViewModel
-import kotlinx.coroutines.launch
import org.json.JSONObject
/**
@@ -31,35 +24,40 @@ abstract class StripeIntentActivity : AppCompatActivity() {
private val stripeAccountId: String? by lazy {
Settings(this).stripeAccountId
}
- protected val stripe: Stripe by lazy {
- StripeFactory(this, stripeAccountId).create()
- }
+
+ private lateinit var paymentLauncher: PaymentLauncher
+
private val keyboardController: KeyboardController by lazy {
KeyboardController(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- viewModel.paymentIntentResultLiveData
- .observe(
+ paymentLauncher =
+ PaymentLauncher.create(
this,
- {
- it.fold(
- onSuccess = ::onConfirmSuccess,
- onFailure = ::onConfirmError
- )
- }
- )
+ PaymentConfiguration.getInstance(this).publishableKey,
+ stripeAccountId
+ ) { paymentResult ->
+ viewModel.status.value += "\n\nPayment authentication completed, getting result"
+ viewModel.paymentResultLiveData.postValue(paymentResult)
+ }
- viewModel.setupIntentResultLiveData
+ viewModel.paymentResultLiveData
.observe(
this,
{
- it.fold(
- onSuccess = ::onConfirmSuccess,
- onFailure = ::onConfirmError
- )
+ when (it) {
+ is PaymentResult.Completed -> {
+ onConfirmSuccess()
+ }
+ is PaymentResult.Canceled -> {
+ onConfirmCanceled()
+ }
+ is PaymentResult.Failed -> {
+ onConfirmError(it)
+ }
+ }
}
)
}
@@ -70,7 +68,8 @@ abstract class StripeIntentActivity : AppCompatActivity() {
shippingDetails: ConfirmPaymentIntentParams.Shipping? = null,
stripeAccountId: String? = null,
existingPaymentMethodId: String? = null,
- mandateDataParams: MandateDataParams? = null
+ mandateDataParams: MandateDataParams? = null,
+ onPaymentIntentCreated: (String) -> Unit = {}
) {
requireNotNull(paymentMethodCreateParams ?: existingPaymentMethodId)
@@ -86,7 +85,8 @@ abstract class StripeIntentActivity : AppCompatActivity() {
shippingDetails,
stripeAccountId,
existingPaymentMethodId,
- mandateDataParams
+ mandateDataParams,
+ onPaymentIntentCreated
)
}
}
@@ -116,9 +116,11 @@ abstract class StripeIntentActivity : AppCompatActivity() {
shippingDetails: ConfirmPaymentIntentParams.Shipping?,
stripeAccountId: String?,
existingPaymentMethodId: String?,
- mandateDataParams: MandateDataParams?
+ mandateDataParams: MandateDataParams?,
+ onPaymentIntentCreated: (String) -> Unit = {}
) {
val secret = responseData.getString("secret")
+ onPaymentIntentCreated(secret)
viewModel.status.postValue(
viewModel.status.value +
"\n\nStarting PaymentIntent confirmation" + (
@@ -140,7 +142,7 @@ abstract class StripeIntentActivity : AppCompatActivity() {
mandateData = mandateDataParams
)
}
- stripe.confirmPayment(this, confirmPaymentIntentParams, stripeAccountId)
+ paymentLauncher.confirm(confirmPaymentIntentParams)
}
private fun handleCreateSetupIntentResponse(
@@ -157,57 +159,27 @@ abstract class StripeIntentActivity : AppCompatActivity() {
} ?: ""
)
)
- stripe.confirmSetupIntent(
- this,
+ paymentLauncher.confirm(
ConfirmSetupIntentParams.create(
paymentMethodCreateParams = params,
clientSecret = secret
- ),
- stripeAccountId
+ )
)
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
-
- keyboardController.hide()
-
- viewModel.status.value += "\n\nPayment authentication completed, getting result"
- if (stripe.isPaymentResult(requestCode, data)) {
- lifecycleScope.launch {
- viewModel.paymentIntentResultLiveData.value = runCatching {
- // stripe.isPaymentResult already verifies data is not null
- stripe.getPaymentIntentResult(requestCode, data!!)
- }
- }
- } else if (stripe.isSetupResult(requestCode, data)) {
- lifecycleScope.launch {
- viewModel.setupIntentResultLiveData.value = runCatching {
- // stripe.isSetupResult already verifies data is not null
- stripe.getSetupIntentResult(requestCode, data!!)
- }
- }
- }
- }
-
- protected open fun onConfirmSuccess(result: PaymentIntentResult) {
- val paymentIntent = result.intent
- viewModel.status.value += "\n\n" +
- "PaymentIntent confirmation outcome: ${result.outcome}\n\n" +
- getString(R.string.payment_intent_status, paymentIntent.status)
+ protected open fun onConfirmSuccess() {
+ viewModel.status.value += "\n\nPaymentIntent confirmation succeeded\n\n"
viewModel.inProgress.value = false
}
- protected open fun onConfirmSuccess(result: SetupIntentResult) {
- val setupIntentResult = result.intent
- viewModel.status.value += "\n\n" +
- "SetupIntent confirmation outcome: ${result.outcome}\n\n" +
- getString(R.string.setup_intent_status, setupIntentResult.status)
+ protected open fun onConfirmCanceled() {
+ viewModel.status.value += "\n\nPaymentIntent confirmation cancelled\n\n"
viewModel.inProgress.value = false
}
- protected open fun onConfirmError(throwable: Throwable) {
- viewModel.status.value += "\n\nException: " + throwable.message
+ protected open fun onConfirmError(failedResult: PaymentResult.Failed) {
+ viewModel.status.value += "\n\nPaymentIntent confirmation failed with throwable " +
+ "${failedResult.throwable} \n\n"
viewModel.inProgress.value = false
}
}
diff --git a/example/src/main/java/com/stripe/example/activity/UpiPaymentActivity.kt b/example/src/main/java/com/stripe/example/activity/UpiPaymentActivity.kt
index 02e5f0907cd..a785f7fef46 100644
--- a/example/src/main/java/com/stripe/example/activity/UpiPaymentActivity.kt
+++ b/example/src/main/java/com/stripe/example/activity/UpiPaymentActivity.kt
@@ -3,10 +3,10 @@ package com.stripe.example.activity
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.Observer
-import com.stripe.android.PaymentIntentResult
import com.stripe.android.model.Address
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
+import com.stripe.android.payments.paymentlauncher.PaymentResult
import com.stripe.example.databinding.UpiPaymentActivityBinding
class UpiPaymentActivity : StripeIntentActivity() {
@@ -14,6 +14,8 @@ class UpiPaymentActivity : StripeIntentActivity() {
UpiPaymentActivityBinding.inflate(layoutInflater)
}
+ private lateinit var paymentIntentSecret: String
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)
@@ -39,20 +41,22 @@ class UpiPaymentActivity : StripeIntentActivity() {
)
)
- createAndConfirmPaymentIntent("in", params)
+ createAndConfirmPaymentIntent("in", params) { secret ->
+ paymentIntentSecret = secret
+ }
}
}
- override fun onConfirmSuccess(result: PaymentIntentResult) {
- val paymentIntent = result.intent
+ override fun onConfirmSuccess() {
startActivity(
Intent(this@UpiPaymentActivity, UpiWaitingActivity::class.java)
- .putExtra(EXTRA_CLIENT_SECRET, paymentIntent.clientSecret)
+ .putExtra(EXTRA_CLIENT_SECRET, paymentIntentSecret)
)
}
- override fun onConfirmError(throwable: Throwable) {
- viewModel.status.value += "\n\nException: " + throwable.message
+ override fun onConfirmError(failedResult: PaymentResult.Failed) {
+ viewModel.status.value += "\n\nPaymentIntent confirmation failed with throwable " +
+ "${failedResult.throwable} \n\n"
}
internal companion object {
diff --git a/example/src/main/java/com/stripe/example/module/StripeIntentViewModel.kt b/example/src/main/java/com/stripe/example/module/StripeIntentViewModel.kt
index 3cbf233e8ac..3fcbe0c436d 100644
--- a/example/src/main/java/com/stripe/example/module/StripeIntentViewModel.kt
+++ b/example/src/main/java/com/stripe/example/module/StripeIntentViewModel.kt
@@ -4,8 +4,7 @@ import android.app.Application
import androidx.annotation.StringRes
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.liveData
-import com.stripe.android.PaymentIntentResult
-import com.stripe.android.SetupIntentResult
+import com.stripe.android.payments.paymentlauncher.PaymentResult
import com.stripe.example.R
import com.stripe.example.activity.BaseViewModel
import kotlinx.coroutines.withContext
@@ -19,8 +18,7 @@ internal class StripeIntentViewModel(
val inProgress = MutableLiveData()
val status = MutableLiveData()
- val paymentIntentResultLiveData = MutableLiveData>()
- val setupIntentResultLiveData = MutableLiveData>()
+ val paymentResultLiveData = MutableLiveData()
fun createPaymentIntent(
country: String,