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

Create rememberLauncher to use Launchers in Compose #5274

Merged
merged 13 commits into from
Jul 27, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## X.X.X

### Payments
* [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.

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ ext {
buildToolsVersion = "30.0.3"
compileSdkVersion = 32

androidxActivityVersion = '1.4.0'
androidxActivityVersion = '1.5.0'
androidxAnnotationVersion = '1.4.0'
androidxAppcompatVersion = '1.4.2'
androidxArchCoreVersion = '2.1.0'
Expand Down
2 changes: 2 additions & 0 deletions example/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
<activity android:name=".activity.PaymentSessionActivity" />
<activity android:name=".activity.GooglePayLauncherIntegrationActivity" />
<activity android:name=".activity.GooglePayLauncherPlaygroundActivity" />
<activity android:name=".activity.GooglePayLauncherComposeActivity" />
<activity android:name=".activity.GooglePayPaymentMethodLauncherIntegrationActivity" />
<activity android:name=".activity.GooglePayPaymentMethodLauncherComposeActivity" />
<activity android:name=".activity.PaymentAuthActivity" />
<activity android:name=".activity.FragmentExamplesActivity" />
<activity android:name=".activity.ConfirmSepaDebitActivity" />
Expand Down
2 changes: 2 additions & 0 deletions example/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
<string name="customer_payment_data_example">Customer Payment Selection</string>
<string name="launch_customer_session">Customer Session</string>
<string name="googlepaylauncher_example">Confirm payment with GooglePayLauncher</string>
<string name="googlepaycomposelauncher_example">Confirm payment in Compose with GooglePayLauncher</string>
<string name="googlepayplayground_example">Test GooglePayLauncher Configurations</string>
<string name="googlepaypaymentmethodlauncher_example">Create payment method with GooglePayPaymentMethodLauncher</string>
<string name="googlepaypaymentmethodcomposelauncher_example">Create payment method in Compose with GooglePayPaymentMethodLauncher</string>
<string name="launch_payment_session">Payment Session</string>
<string name="launch_payment_session_from_fragment">Fragment Examples</string>
<string name="create_payment_method">Create Payment Method</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,43 +46,43 @@ 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
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,
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(
fun rememberPaymentLauncher(): PaymentLauncher {
val context = LocalContext.current
val settings = remember { Settings(context) }
return PaymentLauncher.rememberLauncher(
publishableKey = settings.publishableKey,
stripeAccountId = settings.stripeAccountId
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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.rememberScaffoldState
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 {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()

var clientSecret by rememberSaveable { mutableStateOf("") }
var googlePayReady by rememberSaveable { mutableStateOf<Boolean?>(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
}
}
}
)

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 = {
googlePayLauncher.presentForPaymentIntent(clientSecret)
}
)
)
}
}
}
}

private companion object {
private const val COUNTRY_CODE = "US"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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.rememberScaffoldState
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 {
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
}
}
}
)

Scaffold(scaffoldState = scaffoldState) {
AndroidView(
factory = { context ->
GooglePayButton(context)
},
modifier = Modifier
.wrapContentWidth()
.clickable(
enabled = enabled,
onClick = {
googlePayLauncher.present(
currencyCode = "EUR",
amount = 2500
)
}
)
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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
Expand Down
Loading