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 Apr 20, 2023
1 parent c23a5fa commit f7e25b1
Show file tree
Hide file tree
Showing 16 changed files with 399 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.23.1 - 2023-04-17

### PaymentSheet
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
13 changes: 13 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 @@ -4,6 +4,7 @@ import android.content.Context
import android.os.Parcelable
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.annotation.RestrictTo
import com.stripe.android.core.injection.ENABLE_LOGGING
import com.stripe.android.core.injection.IOContext
Expand Down Expand Up @@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.map
import kotlinx.parcelize.Parcelize
import java.util.UUID
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
Expand Down Expand Up @@ -107,6 +109,17 @@ class LinkPaymentLauncher @Inject internal constructor(
private var linkActivityResultLauncher:
ActivityResultLauncher<LinkActivityContract.Args>? = null

fun register(
activityResultRegistry: ActivityResultRegistry,
callback: (LinkActivityResult) -> Unit,
) {
linkActivityResultLauncher = activityResultRegistry.register(
"LinkHandler_${UUID.randomUUID()}",
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.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,
createIntentCallbackForServerSideConfirmation = 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,
createIntentCallbackForServerSideConfirmation = 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 @@ -776,6 +776,10 @@ public final class com/stripe/android/paymentsheet/PaymentSheetContractV2$Result
public synthetic fun newArray (I)[Ljava/lang/Object;
}

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

public abstract class com/stripe/android/paymentsheet/PaymentSheetResult : android/os/Parcelable {
public static final field $stable I
}
Expand Down Expand Up @@ -1197,6 +1201,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/DefaultFlowControllerKt {
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,106 @@
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
import com.stripe.android.CreateIntentCallback
import com.stripe.android.CreateIntentCallbackForServerSideConfirmation
import com.stripe.android.ExperimentalPaymentSheetDecouplingApi
import com.stripe.android.IntentConfirmationInterceptor

/**
* 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)
}

/**
* Creates a [PaymentSheet] that is remembered across compositions. Use this method when you intend
* to create and confirm 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: CreateIntentCallbackForServerSideConfirmation,
paymentResultCallback: PaymentSheetResultCallback,
): PaymentSheet {
UpdateIntentConfirmationInterceptor(createIntentCallback)
return rememberPaymentSheet(paymentResultCallback)
}

@OptIn(ExperimentalPaymentSheetDecouplingApi::class)
@Composable
private fun UpdateIntentConfirmationInterceptor(
createIntentCallback: CreateIntentCallback,
) {
LaunchedEffect(createIntentCallback) {
IntentConfirmationInterceptor.createIntentCallback = createIntentCallback
}
}

@OptIn(ExperimentalPaymentSheetDecouplingApi::class)
@Composable
private fun UpdateIntentConfirmationInterceptor(
createIntentCallbackForServerSideConfirmation: CreateIntentCallbackForServerSideConfirmation,
) {
LaunchedEffect(createIntentCallbackForServerSideConfirmation) {
IntentConfirmationInterceptor.createIntentCallback = createIntentCallbackForServerSideConfirmation
}
}
Loading

0 comments on commit f7e25b1

Please sign in to comment.