diff --git a/CHANGELOG.md b/CHANGELOG.md
index 423c135a821..8bbe1448766 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,11 +3,13 @@
## X.X.X
### PaymentSheet
-[FIXED][5321](https://github.com/stripe/stripe-android/pull/5321) Fixed issue with forever loading and mochi library.
+* [FIXED][5321](https://github.com/stripe/stripe-android/pull/5321) Fixed issue with forever loading and mochi library.
### Payments
-[FIXED][5308](https://github.com/stripe/stripe-android/pull/5308) OXXO so that processing is considered a successful terminal state, similar to Konbini and Boleto.
-[FIXED][5138](https://github.com/stripe/stripe-android/pull/5138) Fixed an issue where PaymentSheet will show a failure even when 3DS2 Payment/SetupIntent is successful
+* [FIXED][5308](https://github.com/stripe/stripe-android/pull/5308) OXXO so that processing is considered a successful terminal state, similar to Konbini and Boleto.
+* [FIXED][5138](https://github.com/stripe/stripe-android/pull/5138) Fixed an issue where PaymentSheet will show a failure even when 3DS2 Payment/SetupIntent is successful.
+* [ADDED][5274](https://github.com/stripe/stripe-android/pull/5274) Create `rememberLauncher` method enabling usage of `GooglePayLauncher`, `GooglePayPaymentMethodLauncher` and `PaymentLauncher` in Compose.
+* [DEPRECATED][5274](https://github.com/stripe/stripe-android/pull/5274) Deprecate `PaymentLauncher.createForCompose` in favor of `PaymentLauncher.rememberLauncher`.
## 20.7.0 - 2022-07-06
* This release adds additional support for Afterpay/Clearpay in PaymentSheet.
diff --git a/example/AndroidManifest.xml b/example/AndroidManifest.xml
index 838d3b9b8ca..4fff83498f6 100644
--- a/example/AndroidManifest.xml
+++ b/example/AndroidManifest.xml
@@ -50,7 +50,9 @@
+
+
diff --git a/example/res/values/strings.xml b/example/res/values/strings.xml
index ce6667e5445..b1bee1245e6 100644
--- a/example/res/values/strings.xml
+++ b/example/res/values/strings.xml
@@ -13,8 +13,10 @@
Customer Payment Selection
Customer Session
Confirm payment with GooglePayLauncher
+ Confirm payment in Compose with GooglePayLauncher
Test GooglePayLauncher Configurations
Create payment method with GooglePayPaymentMethodLauncher
+ Create payment method in Compose with GooglePayPaymentMethodLauncher
Payment Session
Fragment Examples
Create Payment Method
diff --git a/example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt b/example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt
index e5b421d89e5..0fe7aae7569 100644
--- a/example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt
+++ b/example/src/main/java/com/stripe/example/activity/ComposeExampleActivity.kt
@@ -15,6 +15,7 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -45,33 +46,45 @@ class ComposeExampleActivity : AppCompatActivity() {
fun ComposeScreen() {
val inProgress by viewModel.inProgress.observeAsState(false)
val status by viewModel.status.observeAsState("")
+ val paymentLauncher = rememberPaymentLauncher()
- 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
+ ComposeScreen(
+ inProgress = inProgress,
+ status = status,
+ onConfirm = { paymentLauncher.confirm(it) }
+ )
+ }
+
+ @Composable
+ private fun ComposeScreen(
+ inProgress: Boolean,
+ status: String,
+ onConfirm: (ConfirmPaymentIntentParams) -> Unit
+ ) {
+ Column(modifier = Modifier.padding(horizontal = 10.dp)) {
+ if (inProgress) {
+ LinearProgressIndicator(
+ modifier = Modifier.fillMaxWidth()
)
- Divider(modifier = Modifier.padding(vertical = 5.dp))
- Text(text = status)
}
+ Text(
+ stringResource(R.string.payment_auth_intro),
+ modifier = Modifier.padding(vertical = 5.dp)
+ )
+ ConfirmButton(
+ params = confirmParams3ds1,
+ buttonName = R.string.confirm_with_3ds1_button,
+ onConfirm = onConfirm,
+ inProgress = inProgress
+ )
+ ConfirmButton(
+ params = confirmParams3ds2,
+ buttonName = R.string.confirm_with_3ds2_button,
+ onConfirm = onConfirm,
+ inProgress = inProgress
+ )
+ Divider(modifier = Modifier.padding(vertical = 5.dp))
+ Text(text = status)
}
}
@@ -79,39 +92,43 @@ class ComposeExampleActivity : AppCompatActivity() {
* Create [PaymentLauncher] in a [Composable]
*/
@Composable
- fun createPaymentLauncher(): PaymentLauncher {
- val settings = Settings(LocalContext.current)
- return PaymentLauncher.createForCompose(
+ private fun rememberPaymentLauncher(): PaymentLauncher {
+ val context = LocalContext.current
+ val settings = remember { Settings(context) }
+ return PaymentLauncher.rememberLauncher(
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
- }
+ stripeAccountId = settings.stripeAccountId,
+ callback = ::onPaymentResult
+ )
+ }
+
+ private fun onPaymentResult(paymentResult: PaymentResult) {
+ when (paymentResult) {
+ 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 ${paymentResult.throwable} \n\n"
+ viewModel.inProgress.value = false
}
}
}
@Composable
- fun ConfirmButton(
+ private fun ConfirmButton(
params: PaymentMethodCreateParams,
@StringRes buttonName: Int,
- paymentLauncher: PaymentLauncher,
- inProgress: Boolean
+ inProgress: Boolean,
+ onConfirm: (ConfirmPaymentIntentParams) -> Unit
) {
Button(
- onClick = { createAndConfirmPaymentIntent(params, paymentLauncher) },
+ onClick = { createAndConfirmPaymentIntent(params, onConfirm) },
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp),
@@ -123,7 +140,7 @@ class ComposeExampleActivity : AppCompatActivity() {
private fun createAndConfirmPaymentIntent(
params: PaymentMethodCreateParams,
- paymentLauncher: PaymentLauncher
+ onConfirm: (ConfirmPaymentIntentParams) -> Unit
) {
viewModel.createPaymentIntent("us").observe(
this
@@ -135,7 +152,7 @@ class ComposeExampleActivity : AppCompatActivity() {
clientSecret = responseData.getString("secret"),
shipping = SHIPPING
)
- paymentLauncher.confirm(confirmPaymentIntentParams)
+ onConfirm(confirmPaymentIntentParams)
}
}
}
diff --git a/example/src/main/java/com/stripe/example/activity/GooglePayLauncherComposeActivity.kt b/example/src/main/java/com/stripe/example/activity/GooglePayLauncherComposeActivity.kt
new file mode 100644
index 00000000000..0dfe754a968
--- /dev/null
+++ b/example/src/main/java/com/stripe/example/activity/GooglePayLauncherComposeActivity.kt
@@ -0,0 +1,166 @@
+package com.stripe.example.activity
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.Scaffold
+import androidx.compose.material.ScaffoldState
+import androidx.compose.material.rememberScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.stripe.android.googlepaylauncher.GooglePayEnvironment
+import com.stripe.android.googlepaylauncher.GooglePayLauncher
+import kotlinx.coroutines.launch
+
+class GooglePayLauncherComposeActivity : StripeIntentActivity() {
+ private val googlePayConfig = GooglePayLauncher.Config(
+ environment = GooglePayEnvironment.Test,
+ merchantCountryCode = COUNTRY_CODE,
+ merchantName = "Widget Store",
+ billingAddressConfig = GooglePayLauncher.BillingAddressConfig(
+ isRequired = true,
+ format = GooglePayLauncher.BillingAddressConfig.Format.Full,
+ isPhoneNumberRequired = false
+ ),
+ existingPaymentMethodRequired = false
+ )
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ GooglePayLauncherScreen()
+ }
+ }
+
+ @Composable
+ private fun GooglePayLauncherScreen() {
+ val scaffoldState = rememberScaffoldState()
+ val scope = rememberCoroutineScope()
+
+ var clientSecret by rememberSaveable { mutableStateOf("") }
+ var googlePayReady by rememberSaveable { mutableStateOf(null) }
+ var completed by rememberSaveable { mutableStateOf(false) }
+
+ LaunchedEffect(Unit) {
+ if (clientSecret.isBlank()) {
+ viewModel.createPaymentIntent(COUNTRY_CODE).observe(
+ this@GooglePayLauncherComposeActivity
+ ) { result ->
+ result.fold(
+ onSuccess = { json ->
+ clientSecret = json.getString("secret")
+ },
+ onFailure = { error ->
+ scope.launch {
+ scaffoldState.snackbarHostState.showSnackbar(
+ "Could not create PaymentIntent. ${error.message}"
+ )
+ }
+ completed = true
+ }
+ )
+ }
+ }
+ }
+
+ val googlePayLauncher = GooglePayLauncher.rememberLauncher(
+ config = googlePayConfig,
+ readyCallback = { ready ->
+ if (googlePayReady == null) {
+ googlePayReady = ready
+
+ if (!ready) {
+ completed = true
+ }
+
+ scope.launch {
+ scaffoldState.snackbarHostState.showSnackbar("Google Pay ready? $ready")
+ }
+ }
+ },
+ resultCallback = { result ->
+ when (result) {
+ GooglePayLauncher.Result.Completed -> {
+ "Successfully collected payment."
+ }
+ GooglePayLauncher.Result.Canceled -> {
+ "Customer cancelled Google Pay."
+ }
+ is GooglePayLauncher.Result.Failed -> {
+ "Google Pay failed. ${result.error.message}"
+ }
+ }.let {
+ scope.launch {
+ scaffoldState.snackbarHostState.showSnackbar(it)
+ completed = true
+ }
+ }
+ }
+ )
+
+ GooglePayLauncherScreen(
+ scaffoldState = scaffoldState,
+ clientSecret = clientSecret,
+ googlePayReady = googlePayReady,
+ completed = completed,
+ onLaunchGooglePay = { googlePayLauncher.presentForPaymentIntent(it) }
+ )
+ }
+
+ @Composable
+ private fun GooglePayLauncherScreen(
+ scaffoldState: ScaffoldState,
+ clientSecret: String,
+ googlePayReady: Boolean?,
+ completed: Boolean,
+ onLaunchGooglePay: (String) -> Unit
+ ) {
+ Scaffold(scaffoldState = scaffoldState) {
+ Column(Modifier.fillMaxWidth()) {
+ if (googlePayReady == null || clientSecret.isBlank()) {
+ LinearProgressIndicator(Modifier.fillMaxWidth())
+ }
+
+ Spacer(
+ Modifier
+ .height(8.dp)
+ .fillMaxWidth()
+ )
+
+ AndroidView(
+ factory = { context ->
+ GooglePayButton(context)
+ },
+ modifier = Modifier
+ .wrapContentWidth()
+ .clickable(
+ enabled = googlePayReady == true &&
+ clientSecret.isNotBlank() && !completed,
+ onClick = {
+ onLaunchGooglePay(clientSecret)
+ }
+ )
+ )
+ }
+ }
+ }
+
+ private companion object {
+ private const val COUNTRY_CODE = "US"
+ }
+}
diff --git a/example/src/main/java/com/stripe/example/activity/GooglePayPaymentMethodLauncherComposeActivity.kt b/example/src/main/java/com/stripe/example/activity/GooglePayPaymentMethodLauncherComposeActivity.kt
new file mode 100644
index 00000000000..1a58924c238
--- /dev/null
+++ b/example/src/main/java/com/stripe/example/activity/GooglePayPaymentMethodLauncherComposeActivity.kt
@@ -0,0 +1,112 @@
+package com.stripe.example.activity
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material.Scaffold
+import androidx.compose.material.ScaffoldState
+import androidx.compose.material.rememberScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.stripe.android.googlepaylauncher.GooglePayEnvironment
+import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
+import kotlinx.coroutines.launch
+
+class GooglePayPaymentMethodLauncherComposeActivity : AppCompatActivity() {
+ private val googlePayConfig = GooglePayPaymentMethodLauncher.Config(
+ environment = GooglePayEnvironment.Test,
+ merchantCountryCode = "US",
+ merchantName = "Widget Store",
+ billingAddressConfig = GooglePayPaymentMethodLauncher.BillingAddressConfig(
+ isRequired = true,
+ format = GooglePayPaymentMethodLauncher.BillingAddressConfig.Format.Full,
+ isPhoneNumberRequired = false
+ ),
+ existingPaymentMethodRequired = false
+ )
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ GooglePayPaymentMethodLauncherScreen()
+ }
+ }
+
+ @Composable
+ private fun GooglePayPaymentMethodLauncherScreen() {
+ val scaffoldState = rememberScaffoldState()
+ val scope = rememberCoroutineScope()
+ var enabled by remember { mutableStateOf(false) }
+
+ val googlePayLauncher = GooglePayPaymentMethodLauncher.rememberLauncher(
+ config = googlePayConfig,
+ readyCallback = { ready ->
+ if (ready) {
+ enabled = true
+ }
+ scope.launch {
+ scaffoldState.snackbarHostState.showSnackbar("Google Pay ready? $ready")
+ }
+ },
+ resultCallback = { result ->
+ when (result) {
+ is GooglePayPaymentMethodLauncher.Result.Completed -> {
+ "Successfully created a PaymentMethod. ${result.paymentMethod}"
+ }
+ GooglePayPaymentMethodLauncher.Result.Canceled -> {
+ "Customer cancelled Google Pay."
+ }
+ is GooglePayPaymentMethodLauncher.Result.Failed -> {
+ "Google Pay failed: ${result.errorCode}: ${result.error.message}"
+ }
+ }.let {
+ scope.launch {
+ scaffoldState.snackbarHostState.showSnackbar(it)
+ enabled = false
+ }
+ }
+ }
+ )
+
+ GooglePayPaymentMethodLauncherScreen(
+ scaffoldState = scaffoldState,
+ enabled = enabled,
+ onLaunchGooglePay = {
+ googlePayLauncher.present(
+ currencyCode = "EUR",
+ amount = 2500
+ )
+ }
+ )
+ }
+
+ @Composable
+ private fun GooglePayPaymentMethodLauncherScreen(
+ scaffoldState: ScaffoldState,
+ enabled: Boolean,
+ onLaunchGooglePay: () -> Unit
+ ) {
+ Scaffold(scaffoldState = scaffoldState) {
+ AndroidView(
+ factory = { context ->
+ GooglePayButton(context)
+ },
+ modifier = Modifier
+ .wrapContentWidth()
+ .clickable(
+ enabled = enabled,
+ onClick = onLaunchGooglePay
+ )
+ )
+ }
+ }
+}
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 058ad11c505..6e7e1db3c52 100644
--- a/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt
+++ b/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt
@@ -68,6 +68,10 @@ class LauncherActivity : AppCompatActivity() {
activity.getString(R.string.googlepaylauncher_example),
GooglePayLauncherIntegrationActivity::class.java
),
+ Item(
+ activity.getString(R.string.googlepaycomposelauncher_example),
+ GooglePayLauncherComposeActivity::class.java
+ ),
// This is for internal use so as not to confuse the user.
// Item(
// activity.getString(R.string.googlepayplayground_example),
@@ -77,6 +81,10 @@ class LauncherActivity : AppCompatActivity() {
activity.getString(R.string.googlepaypaymentmethodlauncher_example),
GooglePayPaymentMethodLauncherIntegrationActivity::class.java
),
+ Item(
+ activity.getString(R.string.googlepaypaymentmethodcomposelauncher_example),
+ GooglePayPaymentMethodLauncherComposeActivity::class.java
+ ),
Item(
activity.getString(R.string.launch_confirm_pm_sepa_debit),
ConfirmSepaDebitActivity::class.java
diff --git a/payments-core/api/payments-core.api b/payments-core/api/payments-core.api
index 4c541a42d8c..49bad9fa80a 100644
--- a/payments-core/api/payments-core.api
+++ b/payments-core/api/payments-core.api
@@ -1106,6 +1106,7 @@ public final class com/stripe/android/googlepaylauncher/GooglePayEnvironment : j
public final class com/stripe/android/googlepaylauncher/GooglePayLauncher {
public static final field $stable I
+ public static final field Companion Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$Companion;
public fun (Landroidx/activity/ComponentActivity;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$Config;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$ReadyCallback;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$ResultCallback;)V
public fun (Landroidx/fragment/app/Fragment;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$Config;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$ReadyCallback;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$ResultCallback;)V
public final fun presentForPaymentIntent (Ljava/lang/String;)V
@@ -1144,6 +1145,10 @@ public final class com/stripe/android/googlepaylauncher/GooglePayLauncher$Billin
public static fun values ()[Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$BillingAddressConfig$Format;
}
+public final class com/stripe/android/googlepaylauncher/GooglePayLauncher$Companion {
+ public final fun rememberLauncher (Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$Config;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$ReadyCallback;Lcom/stripe/android/googlepaylauncher/GooglePayLauncher$ResultCallback;Landroidx/compose/runtime/Composer;I)Lcom/stripe/android/googlepaylauncher/GooglePayLauncher;
+}
+
public final class com/stripe/android/googlepaylauncher/GooglePayLauncher$Config : android/os/Parcelable {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
@@ -1348,6 +1353,7 @@ public final class com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLa
public static final field DEVELOPER_ERROR I
public static final field INTERNAL_ERROR I
public static final field NETWORK_ERROR I
+ public synthetic fun (Landroid/content/Context;Lkotlinx/coroutines/CoroutineScope;Landroidx/activity/result/ActivityResultLauncher;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$Config;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$ReadyCallback;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun (Landroidx/activity/ComponentActivity;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$Config;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$ReadyCallback;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$ResultCallback;)V
public fun (Landroidx/fragment/app/Fragment;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$Config;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$ReadyCallback;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$ResultCallback;)V
public final fun present (Ljava/lang/String;)V
@@ -1395,6 +1401,7 @@ public final class com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLa
}
public final class com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$Companion {
+ public final fun rememberLauncher (Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$Config;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$ReadyCallback;Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$ResultCallback;Landroidx/compose/runtime/Composer;I)Lcom/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher;
}
public final class com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher$Config : android/os/Parcelable {
@@ -6898,6 +6905,7 @@ public final class com/stripe/android/payments/paymentlauncher/PaymentLauncher$C
public static synthetic fun create$default (Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher$Companion;Landroidx/activity/ComponentActivity;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher$PaymentResultCallback;ILjava/lang/Object;)Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher;
public static synthetic fun create$default (Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher$Companion;Landroidx/fragment/app/Fragment;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher$PaymentResultCallback;ILjava/lang/Object;)Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher;
public final fun createForCompose (Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher$PaymentResultCallback;Landroidx/compose/runtime/Composer;II)Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher;
+ public final fun rememberLauncher (Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher$PaymentResultCallback;Landroidx/compose/runtime/Composer;II)Lcom/stripe/android/payments/paymentlauncher/PaymentLauncher;
}
public abstract interface class com/stripe/android/payments/paymentlauncher/PaymentLauncher$PaymentResultCallback {
diff --git a/payments-core/build.gradle b/payments-core/build.gradle
index 3b3dd5f2d8b..a6f59f50b68 100644
--- a/payments-core/build.gradle
+++ b/payments-core/build.gradle
@@ -19,26 +19,24 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:$androidxFragmentVersion"
implementation "androidx.constraintlayout:constraintlayout:$androidxConstraintlayoutVersion"
implementation "androidx.activity:activity-ktx:$androidxActivityVersion"
+ implementation "androidx.activity:activity-compose:$androidxActivityVersion"
implementation "com.google.android.gms:play-services-wallet:$playServicesWalletVersion"
implementation "com.google.android.material:material:$materialVersion"
implementation "com.google.dagger:dagger:$daggerVersion"
- kapt "com.google.dagger:dagger-compiler:$daggerVersion"
-
- implementation "androidx.activity:activity-compose:$androidxActivityVersion"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"
implementation 'com.google.android.instantapps:instantapps:1.1.0'
-
// For instructions on replacing the BouncyCastle dependency used by the 3DS2 SDK, see
// https://github.com/stripe/stripe-android/issues/3173#issuecomment-785176608
implementation "com.stripe:stripe-3ds2-android:6.1.5"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"
+ kapt "com.google.dagger:dagger-compiler:$daggerVersion"
javadocDeps "androidx.annotation:annotation:$androidxAnnotationVersion"
javadocDeps "androidx.appcompat:appcompat:$androidxAppcompatVersion"
javadocDeps "com.google.android.material:material:$materialVersion"
- testImplementation project(':financial-connections')
+ testImplementation project(':financial-connections')
testImplementation "junit:junit:$junitVersion"
testImplementation "org.mockito:mockito-core:$mockitoCoreVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"
@@ -56,6 +54,7 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test:rules:$androidTestVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion"
+
androidTestUtil "androidx.test:orchestrator:$androidTestVersion"
ktlint "com.pinterest:ktlint:$ktlintVersion"
diff --git a/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayLauncher.kt b/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayLauncher.kt
index 5123bae901c..6ba57496a4e 100644
--- a/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayLauncher.kt
+++ b/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayLauncher.kt
@@ -2,7 +2,14 @@ package com.stripe.android.googlepaylauncher
import android.os.Parcelable
import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.stripe.android.PaymentConfiguration
@@ -276,7 +283,58 @@ class GooglePayLauncher internal constructor(
fun onResult(result: Result)
}
- internal companion object {
+ companion object {
internal const val PRODUCT_USAGE = "GooglePayLauncher"
+
+ /**
+ * Create a [GooglePayLauncher] used for Jetpack Compose.
+ *
+ * This API uses Compose specific API [rememberLauncherForActivityResult] to register a
+ * [ActivityResultLauncher] into current activity, it should be called as part of Compose
+ * initialization path.
+ * The GooglePayLauncher created is remembered across recompositions. Recomposition will
+ * always return the value produced by composition.
+ */
+ @Composable
+ fun rememberLauncher(
+ config: Config,
+ readyCallback: ReadyCallback,
+ resultCallback: ResultCallback
+ ): GooglePayLauncher {
+ val currentReadyCallback by rememberUpdatedState(readyCallback)
+
+ val context = LocalContext.current
+ val lifecycleScope = LocalLifecycleOwner.current.lifecycleScope
+ val activityResultLauncher = rememberLauncherForActivityResult(
+ GooglePayLauncherContract(),
+ resultCallback::onResult
+ )
+
+ return remember(config) {
+ GooglePayLauncher(
+ lifecycleScope = lifecycleScope,
+ config = config,
+ readyCallback = {
+ currentReadyCallback.onReady(it)
+ },
+ activityResultLauncher = activityResultLauncher,
+ googlePayRepositoryFactory = {
+ DefaultGooglePayRepository(
+ context = context,
+ environment = config.environment,
+ billingAddressParameters = config.billingAddressConfig.convert(),
+ existingPaymentMethodRequired = config.existingPaymentMethodRequired,
+ allowCreditCards = config.allowCreditCards
+ )
+ },
+ PaymentAnalyticsRequestFactory(
+ context,
+ PaymentConfiguration.getInstance(context).publishableKey,
+ setOf(PRODUCT_USAGE)
+ ),
+ DefaultAnalyticsRequestExecutor()
+ )
+ }
+ }
}
}
diff --git a/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher.kt b/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher.kt
index 8b0cf1575c0..bfe201cb43e 100644
--- a/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher.kt
+++ b/payments-core/src/main/java/com/stripe/android/googlepaylauncher/GooglePayPaymentMethodLauncher.kt
@@ -3,9 +3,15 @@ package com.stripe.android.googlepaylauncher
import android.content.Context
import android.os.Parcelable
import androidx.activity.ComponentActivity
-import androidx.activity.result.ActivityResultCaller
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IntDef
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.stripe.android.BuildConfig
@@ -129,10 +135,13 @@ class GooglePayPaymentMethodLauncher @AssistedInject internal constructor(
) : this(
activity,
activity.lifecycleScope,
- activity,
+ activity.registerForActivityResult(
+ GooglePayPaymentMethodLauncherContract()
+ ) {
+ resultCallback.onResult(it)
+ },
config,
- readyCallback,
- resultCallback
+ readyCallback
)
/**
@@ -154,28 +163,26 @@ class GooglePayPaymentMethodLauncher @AssistedInject internal constructor(
) : this(
fragment.requireContext(),
fragment.viewLifecycleOwner.lifecycleScope,
- fragment,
+ fragment.registerForActivityResult(
+ GooglePayPaymentMethodLauncherContract()
+ ) {
+ resultCallback.onResult(it)
+ },
config,
- readyCallback,
- resultCallback
+ readyCallback
)
private constructor (
context: Context,
lifecycleScope: CoroutineScope,
- activityResultCaller: ActivityResultCaller,
+ activityResultLauncher: ActivityResultLauncher,
config: Config,
- readyCallback: ReadyCallback,
- resultCallback: ResultCallback
+ readyCallback: ReadyCallback
) : this(
lifecycleScope,
config,
readyCallback,
- activityResultCaller.registerForActivityResult(
- GooglePayPaymentMethodLauncherContract()
- ) {
- resultCallback.onResult(it)
- },
+ activityResultLauncher,
false,
context,
googlePayRepositoryFactory = {
@@ -376,5 +383,42 @@ class GooglePayPaymentMethodLauncher @AssistedInject internal constructor(
// Error executing a network call
const val NETWORK_ERROR = 3
+
+ /**
+ * Create a [GooglePayPaymentMethodLauncher] used for Jetpack Compose.
+ *
+ * This API uses Compose specific API [rememberLauncherForActivityResult] to register a
+ * [ActivityResultLauncher] into current activity, it should be called as part of Compose
+ * initialization path.
+ * The GooglePayPaymentMethodLauncher created is remembered across recompositions.
+ * Recomposition will always return the value produced by composition.
+ */
+ @Composable
+ fun rememberLauncher(
+ config: Config,
+ readyCallback: ReadyCallback,
+ resultCallback: ResultCallback
+ ): GooglePayPaymentMethodLauncher {
+ val currentReadyCallback by rememberUpdatedState(readyCallback)
+
+ val context = LocalContext.current
+ val lifecycleScope = LocalLifecycleOwner.current.lifecycleScope
+ val activityResultLauncher = rememberLauncherForActivityResult(
+ GooglePayPaymentMethodLauncherContract(),
+ resultCallback::onResult
+ )
+
+ return remember(config) {
+ GooglePayPaymentMethodLauncher(
+ context = context,
+ lifecycleScope = lifecycleScope,
+ activityResultLauncher = activityResultLauncher,
+ config = config,
+ readyCallback = {
+ currentReadyCallback.onReady(it)
+ }
+ )
+ }
+ }
}
}
diff --git a/payments-core/src/main/java/com/stripe/android/payments/paymentlauncher/PaymentLauncher.kt b/payments-core/src/main/java/com/stripe/android/payments/paymentlauncher/PaymentLauncher.kt
index ecaf4f307c7..9c6775864fa 100644
--- a/payments-core/src/main/java/com/stripe/android/payments/paymentlauncher/PaymentLauncher.kt
+++ b/payments-core/src/main/java/com/stripe/android/payments/paymentlauncher/PaymentLauncher.kt
@@ -4,6 +4,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.fragment.app.Fragment
import com.stripe.android.model.ConfirmPaymentIntentParams
@@ -75,7 +76,14 @@ interface PaymentLauncher {
* This API uses Compose specific API [rememberLauncherForActivityResult] to register a
* [ActivityResultLauncher] into current activity, it should be called as part of Compose
* initialization path.
+ * This method creates a new PaymentLauncher object every time it is called, even during
+ * recompositions.
*/
+ @Deprecated(
+ "This method creates a new PaymentLauncher object every time it is called, even" +
+ "during recompositions.",
+ replaceWith = ReplaceWith("PaymentLauncher.rememberLauncher(publishableKey, stripeAccountId, callback)")
+ )
@Composable
fun createForCompose(
publishableKey: String,
@@ -88,5 +96,34 @@ interface PaymentLauncher {
callback::onPaymentResult
)
).create(publishableKey, stripeAccountId)
+
+ /**
+ * Create a [PaymentLauncher] used for Jetpack Compose.
+ *
+ * This API uses Compose specific API [rememberLauncherForActivityResult] to register a
+ * [ActivityResultLauncher] into current activity, it should be called as part of Compose
+ * initialization path.
+ * The PaymentLauncher created is remembered across recompositions. Recomposition will
+ * always return the value produced by composition.
+ */
+ @Composable
+ fun rememberLauncher(
+ publishableKey: String,
+ stripeAccountId: String? = null,
+ callback: PaymentResultCallback
+ ): PaymentLauncher {
+ val context = LocalContext.current
+ val activityResultLauncher = rememberLauncherForActivityResult(
+ PaymentLauncherContract(),
+ callback::onPaymentResult
+ )
+
+ return remember(publishableKey, stripeAccountId) {
+ PaymentLauncherFactory(
+ context,
+ activityResultLauncher
+ ).create(publishableKey, stripeAccountId)
+ }
+ }
}
}