Skip to content

Commit

Permalink
Add remember methods for PaymentSheet integration in Compose
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Jun 15, 2023
1 parent d833049 commit 6b20df7
Show file tree
Hide file tree
Showing 17 changed files with 330 additions and 101 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## XX.XX.XX - 2023-XX-XX

### PaymentSheet
[ADDED][6583](https://github.com/stripe/stripe-android/pull/6583) Added dedicated remember methods for `PaymentSheet` and `PaymentSheet.FlowController` for easier integration in Compose.
[DEPRECATED][6583](https://github.com/stripe/stripe-android/pull/6583) Deprecated `PaymentSheetContract` and `PaymentSheetContract.Args`. Use the `PaymentSheet` constructor or new `rememberPaymentSheet` method instead.

## 20.25.6 - 2023-06-12

### Financial Connections
Expand Down
4 changes: 4 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Migration Guide

## Migrating from versions < 20.20.0
- Changes to `PaymentSheetContract`:
* `PaymentSheetContract` and `PaymentSheetContract.Args` are now deprecated and will be removed in a future release. To create and open a `PaymentSheet`, use the constructor or the new `rememberPaymentSheet()` method.

## Migrating from versions < 20.5.0
- Changes to `PaymentSheet.Configuration`
* `primaryButtonColor` is now deprecated. Please use the new `Appearance` parameter instead:
Expand Down
12 changes: 12 additions & 0 deletions link/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.stripe.android.link

import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.annotation.RestrictTo
import com.stripe.android.link.ui.paymentmethod.SupportedPaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
Expand All @@ -17,6 +18,17 @@ class LinkPaymentLauncher @Inject internal constructor() {
private var linkActivityResultLauncher:
ActivityResultLauncher<LinkActivityContract.Args>? = null

fun register(
activityResultRegistry: ActivityResultRegistry,
callback: (LinkActivityResult) -> Unit,
) {
linkActivityResultLauncher = activityResultRegistry.register(
"LinkPaymentLauncher",
LinkActivityContract(),
callback,
)
}

fun register(
activityResultCaller: ActivityResultCaller,
callback: (LinkActivityResult) -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.stripe.android.paymentsheet.example.samples.ui.shared.BuyButton
import com.stripe.android.paymentsheet.example.samples.ui.shared.CompletedPaymentAlertDialog
import com.stripe.android.paymentsheet.example.samples.ui.shared.PaymentSheetExampleTheme
import com.stripe.android.paymentsheet.example.samples.ui.shared.Receipt
import com.stripe.android.paymentsheet.rememberPaymentSheet

internal class CompleteFlowActivity : AppCompatActivity() {

Expand All @@ -25,23 +26,20 @@ internal class CompleteFlowActivity : AppCompatActivity() {

private val viewModel by viewModels<CompleteFlowViewModel>()

private lateinit var paymentSheet: PaymentSheet

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

paymentSheet = PaymentSheet(
activity = this,
callback = viewModel::handlePaymentSheetResult,
)

setContent {
val paymentSheet = rememberPaymentSheet(
paymentResultCallback = viewModel::handlePaymentSheetResult,
)

PaymentSheetExampleTheme {
val uiState by viewModel.state.collectAsState()

uiState.paymentInfo?.let { paymentInfo ->
LaunchedEffect(paymentInfo) {
presentPaymentSheet(paymentInfo)
presentPaymentSheet(paymentSheet, paymentInfo)
}
}

Expand Down Expand Up @@ -71,7 +69,10 @@ internal class CompleteFlowActivity : AppCompatActivity() {
}
}

private fun presentPaymentSheet(paymentInfo: CompleteFlowViewState.PaymentInfo) {
private fun presentPaymentSheet(
paymentSheet: PaymentSheet,
paymentInfo: CompleteFlowViewState.PaymentInfo,
) {
if (!paymentInfo.shouldPresent) {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.stripe.android.paymentsheet.example.samples.ui.shared.ErrorScreen
import com.stripe.android.paymentsheet.example.samples.ui.shared.PaymentMethodSelector
import com.stripe.android.paymentsheet.example.samples.ui.shared.PaymentSheetExampleTheme
import com.stripe.android.paymentsheet.example.samples.ui.shared.Receipt
import com.stripe.android.paymentsheet.flowcontroller.rememberPaymentSheetFlowController

internal class CustomFlowActivity : AppCompatActivity() {

Expand All @@ -31,25 +32,22 @@ internal class CustomFlowActivity : AppCompatActivity() {

private val viewModel by viewModels<CustomFlowViewModel>()

private lateinit var flowController: PaymentSheet.FlowController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

flowController = PaymentSheet.FlowController.create(
activity = this,
paymentOptionCallback = viewModel::handlePaymentOptionChanged,
paymentResultCallback = viewModel::handlePaymentSheetResult,
)

setContent {
val flowController = rememberPaymentSheetFlowController(
paymentOptionCallback = viewModel::handlePaymentOptionChanged,
paymentResultCallback = viewModel::handlePaymentSheetResult,
)

PaymentSheetExampleTheme {
val uiState by viewModel.state.collectAsState()
val paymentMethodLabel = determinePaymentMethodLabel(uiState)

uiState.paymentInfo?.let { paymentInfo ->
LaunchedEffect(paymentInfo) {
configureFlowController(paymentInfo)
configureFlowController(flowController, paymentInfo)
}
}

Expand Down Expand Up @@ -92,7 +90,10 @@ internal class CustomFlowActivity : AppCompatActivity() {
}
}

private fun configureFlowController(paymentInfo: CustomFlowViewState.PaymentInfo) {
private fun configureFlowController(
flowController: PaymentSheet.FlowController,
paymentInfo: CustomFlowViewState.PaymentInfo,
) {
flowController.configureWithPaymentIntent(
paymentIntentClientSecret = paymentInfo.clientSecret,
configuration = paymentInfo.paymentSheetConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.google.android.material.snackbar.Snackbar
import com.stripe.android.paymentsheet.ExperimentalPaymentSheetDecouplingApi
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.example.samples.model.toIntentConfiguration
import com.stripe.android.paymentsheet.example.samples.ui.shared.BuyButton
import com.stripe.android.paymentsheet.example.samples.ui.shared.CompletedPaymentAlertDialog
import com.stripe.android.paymentsheet.example.samples.ui.shared.ErrorScreen
import com.stripe.android.paymentsheet.example.samples.ui.shared.PaymentSheetExampleTheme
import com.stripe.android.paymentsheet.example.samples.ui.shared.Receipt
import com.stripe.android.paymentsheet.example.samples.ui.shared.SubscriptionToggle
import com.stripe.android.paymentsheet.rememberPaymentSheet

internal class ServerSideConfirmationCompleteFlowActivity : AppCompatActivity() {

Expand All @@ -29,20 +29,17 @@ internal class ServerSideConfirmationCompleteFlowActivity : AppCompatActivity()

private val viewModel by viewModels<ServerSideConfirmationCompleteFlowViewModel>()

private lateinit var paymentSheet: PaymentSheet

@OptIn(ExperimentalPaymentSheetDecouplingApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

paymentSheet = PaymentSheet(
activity = this,
createIntentCallback = viewModel::createAndConfirmIntent,
paymentResultCallback = viewModel::handlePaymentSheetResult,
)

setContent {
PaymentSheetExampleTheme {
val paymentSheet = rememberPaymentSheet(
createIntentCallback = viewModel::createAndConfirmIntent,
paymentResultCallback = viewModel::handlePaymentSheetResult,
)

val uiState by viewModel.state.collectAsState()

uiState.status?.let { status ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.stripe.android.paymentsheet.example.samples.ui.shared.PaymentMethodSe
import com.stripe.android.paymentsheet.example.samples.ui.shared.PaymentSheetExampleTheme
import com.stripe.android.paymentsheet.example.samples.ui.shared.Receipt
import com.stripe.android.paymentsheet.example.samples.ui.shared.SubscriptionToggle
import com.stripe.android.paymentsheet.flowcontroller.rememberPaymentSheetFlowController
import kotlinx.coroutines.CompletableDeferred

internal class ServerSideConfirmationCustomFlowActivity : AppCompatActivity() {
Expand All @@ -37,25 +38,22 @@ internal class ServerSideConfirmationCustomFlowActivity : AppCompatActivity() {

private val viewModel by viewModels<ServerSideConfirmationCustomFlowViewModel>()

private lateinit var flowController: PaymentSheet.FlowController

@OptIn(ExperimentalPaymentSheetDecouplingApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

flowController = PaymentSheet.FlowController.create(
activity = this,
paymentOptionCallback = viewModel::handlePaymentOptionChanged,
createIntentCallback = viewModel::createAndConfirmIntent,
paymentResultCallback = viewModel::handlePaymentSheetResult,
)

setContent {
PaymentSheetExampleTheme {
val flowController = rememberPaymentSheetFlowController(
createIntentCallback = viewModel::createAndConfirmIntent,
paymentOptionCallback = viewModel::handlePaymentOptionChanged,
paymentResultCallback = viewModel::handlePaymentSheetResult,
)

val uiState by viewModel.state.collectAsState()
val paymentMethodLabel = determinePaymentMethodLabel(uiState)

AttachFlowControllerToViewModel(uiState)
AttachFlowControllerToViewModel(flowController, uiState)

uiState.status?.let { status ->
if (uiState.didComplete) {
Expand Down Expand Up @@ -107,6 +105,7 @@ internal class ServerSideConfirmationCustomFlowActivity : AppCompatActivity() {
@OptIn(ExperimentalPaymentSheetDecouplingApi::class)
@Composable
fun AttachFlowControllerToViewModel(
flowController: PaymentSheet.FlowController,
uiState: ServerSideConfirmationCustomFlowViewState,
) {
DisposableEffect(Unit) {
Expand Down
8 changes: 8 additions & 0 deletions paymentsheet/api/paymentsheet.api
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,10 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Typography$Creat
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/paymentsheet/PaymentSheetComposeKt {
public static final fun rememberPaymentSheet (Lcom/stripe/android/paymentsheet/PaymentSheetResultCallback;Landroidx/compose/runtime/Composer;I)Lcom/stripe/android/paymentsheet/PaymentSheet;
}

public final class com/stripe/android/paymentsheet/PaymentSheetContract : androidx/activity/result/contract/ActivityResultContract {
public static final field $stable I
public static final field EXTRA_ARGS Ljava/lang/String;
Expand Down Expand Up @@ -1221,6 +1225,10 @@ public final class com/stripe/android/paymentsheet/flowcontroller/DefaultFlowCon
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/paymentsheet/flowcontroller/FlowControllerComposeKt {
public static final fun rememberPaymentSheetFlowController (Lcom/stripe/android/paymentsheet/PaymentOptionCallback;Lcom/stripe/android/paymentsheet/PaymentSheetResultCallback;Landroidx/compose/runtime/Composer;I)Lcom/stripe/android/paymentsheet/PaymentSheet$FlowController;
}

public final class com/stripe/android/paymentsheet/model/PaymentIntentClientSecret$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/model/PaymentIntentClientSecret;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.stripe.android.paymentsheet

import android.app.Application
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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

/**
* Creates a [PaymentSheet] that is remembered across compositions.
*
* This *must* be called unconditionally, as part of the initialization path.
*
* @param paymentResultCallback Called with the result of the payment after [PaymentSheet] is dismissed.
*/
@Composable
fun rememberPaymentSheet(
paymentResultCallback: PaymentSheetResultCallback,
): PaymentSheet {
val onResult by rememberUpdatedState(newValue = paymentResultCallback::onPaymentSheetResult)

val activityResultLauncher = rememberLauncherForActivityResult(
contract = PaymentSheetContractV2(),
onResult = onResult,
)

val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current

return remember {
val launcher = DefaultPaymentSheetLauncher(
activityResultLauncher = activityResultLauncher,
application = context.applicationContext as Application,
lifecycleOwner = lifecycleOwner,
)
PaymentSheet(launcher)
}
}

/**
* Creates a [PaymentSheet] that is remembered across compositions. Use this method when you intend
* to create the [com.stripe.android.model.PaymentIntent] or [com.stripe.android.model.SetupIntent]
* on your server.
*
* This *must* be called unconditionally, as part of the initialization path.
*
* @param createIntentCallback Called when the customer confirms the payment or setup.
* @param paymentResultCallback Called with the result of the payment after [PaymentSheet] is dismissed.
*/
@ExperimentalPaymentSheetDecouplingApi
@Composable
fun rememberPaymentSheet(
createIntentCallback: CreateIntentCallback,
paymentResultCallback: PaymentSheetResultCallback,
): PaymentSheet {
UpdateIntentConfirmationInterceptor(createIntentCallback)
return rememberPaymentSheet(paymentResultCallback)
}

@OptIn(ExperimentalPaymentSheetDecouplingApi::class)
@Composable
private fun UpdateIntentConfirmationInterceptor(
createIntentCallback: CreateIntentCallback,
) {
LaunchedEffect(createIntentCallback) {
IntentConfirmationInterceptor.createIntentCallback = createIntentCallback
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import com.stripe.android.paymentsheet.model.SetupIntentClientSecret
import com.stripe.android.view.ActivityStarter
import kotlinx.parcelize.Parcelize

@Suppress("DEPRECATION")
@Deprecated(
message = "This isn't meant for public usage and will be removed in a future " +
"release. If you're looking to integrate with PaymentSheet in Compose, " +
"use rememberPaymentSheet() instead.",
)
class PaymentSheetContract :
ActivityResultContract<PaymentSheetContract.Args, PaymentSheetResult>() {
override fun createIntent(
Expand Down
Loading

0 comments on commit 6b20df7

Please sign in to comment.