diff --git a/paymentsheet/api/paymentsheet.api b/paymentsheet/api/paymentsheet.api index 30b551c9efa..fc3f8db7bf0 100644 --- a/paymentsheet/api/paymentsheet.api +++ b/paymentsheet/api/paymentsheet.api @@ -542,6 +542,30 @@ public final class com/stripe/android/lpmfoundations/paymentmethod/link/LinkInli public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/paymentelement/CustomPaymentMethodResult$Canceled$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentelement/CustomPaymentMethodResult$Canceled; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentelement/CustomPaymentMethodResult$Canceled; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/stripe/android/paymentelement/CustomPaymentMethodResult$Completed$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentelement/CustomPaymentMethodResult$Completed; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentelement/CustomPaymentMethodResult$Completed; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/stripe/android/paymentelement/CustomPaymentMethodResult$Failed$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentelement/CustomPaymentMethodResult$Failed; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentelement/CustomPaymentMethodResult$Failed; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/paymentelement/EmbeddedPaymentElement$Configuration$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentelement/EmbeddedPaymentElement$Configuration; @@ -1333,6 +1357,14 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Configuration$Cr public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/paymentsheet/PaymentSheet$CustomPaymentMethod$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/PaymentSheet$CustomPaymentMethod; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentsheet/PaymentSheet$CustomPaymentMethod; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/paymentsheet/PaymentSheet$CustomerAccessType$CustomerSession$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerAccessType$CustomerSession; diff --git a/paymentsheet/src/main/java/com/stripe/android/common/configuration/ConfigurationDefaults.kt b/paymentsheet/src/main/java/com/stripe/android/common/configuration/ConfigurationDefaults.kt index 2a104de3875..30b8a371069 100644 --- a/paymentsheet/src/main/java/com/stripe/android/common/configuration/ConfigurationDefaults.kt +++ b/paymentsheet/src/main/java/com/stripe/android/common/configuration/ConfigurationDefaults.kt @@ -26,5 +26,7 @@ internal object ConfigurationDefaults { val externalPaymentMethods: List = emptyList() val paymentMethodLayout: PaymentMethodLayout = PaymentMethodLayout.Automatic val cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = PaymentSheet.CardBrandAcceptance.All + val customPaymentMethods: List = emptyList() + const val embeddedViewDisplaysMandateText: Boolean = true } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/CustomPaymentMethodConfirmHandler.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/CustomPaymentMethodConfirmHandler.kt new file mode 100644 index 00000000000..c36890e68e6 --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/CustomPaymentMethodConfirmHandler.kt @@ -0,0 +1,29 @@ +package com.stripe.android.paymentelement + +import androidx.annotation.RestrictTo +import com.stripe.android.model.PaymentMethod +import com.stripe.android.paymentsheet.PaymentSheet + +/** + * Handler to be used to confirm payment with a custom payment method. + * + * To learn more about custom payment methods, see "docs_url" + */ +@ExperimentalCustomPaymentMethodsApi +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +fun interface CustomPaymentMethodConfirmHandler { + + /** + * Called when a user confirms payment or setup with a custom payment method. + * + * On completion, this should call [CustomPaymentMethodResultHandler.onCustomPaymentMethodResult] with the + * result of the custom payment method's confirmation. + * + * @param customPaymentMethod The custom payment method to confirm payment with + * @param billingDetails Any billing details you've configured Payment Element to collect + */ + fun confirmCustomPaymentMethod( + customPaymentMethod: PaymentSheet.CustomPaymentMethod, + billingDetails: PaymentMethod.BillingDetails, + ) +} diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/CustomPaymentMethodResultHandler.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/CustomPaymentMethodResultHandler.kt new file mode 100644 index 00000000000..99db342f58a --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/CustomPaymentMethodResultHandler.kt @@ -0,0 +1,81 @@ +package com.stripe.android.paymentelement + +import android.content.Context +import android.os.Parcelable +import androidx.annotation.RestrictTo +import kotlinx.parcelize.Parcelize + +/** + * Handler used to respond to custom payment method confirm results. + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +object CustomPaymentMethodResultHandler { + + /** + * Updates the Payment Element UI to reflect the result of confirming a custom payment method. + * + * Should be called when [CustomPaymentMethodConfirmHandler.confirmCustomPaymentMethod] completes. + */ + @JvmStatic + fun onCustomPaymentMethodResult(context: Context, customPaymentMethodResult: CustomPaymentMethodResult) { + error( + "Not implemented! Should not called with context from " + + "${context.packageName} and $customPaymentMethodResult" + ) + } +} + +/** + * The result of an attempt to confirm a custom payment method. + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +sealed class CustomPaymentMethodResult : Parcelable { + @Parcelize + internal data object Completed : CustomPaymentMethodResult() + + @Parcelize + internal data object Canceled : CustomPaymentMethodResult() + + @Parcelize + internal data class Failed( + val displayMessage: String?, + ) : CustomPaymentMethodResult() + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + companion object { + + /** + * The customer successfully completed the payment or setup. + */ + @JvmStatic + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun completed(): CustomPaymentMethodResult { + return Completed + } + + /** + * The customer canceled the payment or setup attempt. + */ + @JvmStatic + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun canceled(): CustomPaymentMethodResult { + return Canceled + } + + /** + * The payment or setup attempt failed. + * + * @param displayMessage Message to display to the user on failure. If null, will display Stripe's default + * error message. + */ + @JvmStatic + @JvmOverloads + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun failed(displayMessage: String? = null): CustomPaymentMethodResult { + return Failed(displayMessage) + } + } +} diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElement.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElement.kt index c12536b0e40..1513c4a1156 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElement.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElement.kt @@ -115,12 +115,25 @@ class EmbeddedPaymentElement @Inject internal constructor( internal var externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler? = null private set + @OptIn(ExperimentalCustomPaymentMethodsApi::class) + internal var customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler? = null + private set + /** * Called when a user confirms payment for an external payment method. */ fun externalPaymentMethodConfirmHandler(handler: ExternalPaymentMethodConfirmHandler) = apply { this.externalPaymentMethodConfirmHandler = handler } + + /** + * Called when a user confirms payment for a custom payment method. + */ + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun customPaymentMethodConfirmHandler(handler: CustomPaymentMethodConfirmHandler) = apply { + this.customPaymentMethodConfirmHandler = handler + } } /** Configuration for [EmbeddedPaymentElement] **/ @@ -144,6 +157,7 @@ class EmbeddedPaymentElement @Inject internal constructor( internal val paymentMethodOrder: List, internal val externalPaymentMethods: List, internal val cardBrandAcceptance: PaymentSheet.CardBrandAcceptance, + internal val customPaymentMethods: List, internal val embeddedViewDisplaysMandateText: Boolean, ) : Parcelable { @Suppress("TooManyFunctions") @@ -174,6 +188,8 @@ class EmbeddedPaymentElement @Inject internal constructor( private var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = ConfigurationDefaults.cardBrandAcceptance private var embeddedViewDisplaysMandateText: Boolean = ConfigurationDefaults.embeddedViewDisplaysMandateText + private var customPaymentMethods: List = + ConfigurationDefaults.customPaymentMethods /** * If set, the customer can select a previously saved payment method. @@ -330,6 +346,19 @@ class EmbeddedPaymentElement @Inject internal constructor( this.cardBrandAcceptance = cardBrandAcceptance } + /** + * Configuration related to custom payment methods. + * + * If set, Embedded Payment Element will display the defined list of custom payment methods in the UI. + */ + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun customPaymentMethods( + customPaymentMethods: List, + ) = apply { + this.customPaymentMethods = customPaymentMethods + } + /** * Controls whether the view displays mandate text at the bottom for payment methods that require it. * @@ -360,6 +389,7 @@ class EmbeddedPaymentElement @Inject internal constructor( paymentMethodOrder = paymentMethodOrder, externalPaymentMethods = externalPaymentMethods, cardBrandAcceptance = cardBrandAcceptance, + customPaymentMethods = customPaymentMethods, embeddedViewDisplaysMandateText = embeddedViewDisplaysMandateText, ) } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElementKtx.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElementKtx.kt index 146dc432529..c38ba08b28c 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElementKtx.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElementKtx.kt @@ -35,8 +35,10 @@ fun rememberEmbeddedPaymentElement( } val callbacks = remember(builder) { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) PaymentElementCallbacks( createIntentCallback = builder.createIntentCallback, + customPaymentMethodConfirmHandler = builder.customPaymentMethodConfirmHandler, externalPaymentMethodConfirmHandler = builder.externalPaymentMethodConfirmHandler, ) } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/ExperimentalCustomPaymentMethodsApi.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/ExperimentalCustomPaymentMethodsApi.kt new file mode 100644 index 00000000000..bbcb0940608 --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/ExperimentalCustomPaymentMethodsApi.kt @@ -0,0 +1,11 @@ +package com.stripe.android.paymentelement + +import androidx.annotation.RestrictTo + +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, + message = "Custom payment methods support is beta. It may be changed in the future without notice." +) +@Retention(AnnotationRetention.BINARY) +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +annotation class ExperimentalCustomPaymentMethodsApi diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbacks.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbacks.kt index 4f5939569b6..efa45a5dd61 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbacks.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbacks.kt @@ -1,9 +1,13 @@ package com.stripe.android.paymentelement.callbacks +import com.stripe.android.paymentelement.CustomPaymentMethodConfirmHandler +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentsheet.CreateIntentCallback import com.stripe.android.paymentsheet.ExternalPaymentMethodConfirmHandler +@OptIn(ExperimentalCustomPaymentMethodsApi::class) internal class PaymentElementCallbacks( val createIntentCallback: CreateIntentCallback?, + val customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler?, val externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler?, ) diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FlowControllerCompose.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FlowControllerCompose.kt index e49b58ad747..e7bbd247120 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FlowControllerCompose.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FlowControllerCompose.kt @@ -7,6 +7,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import com.stripe.android.common.ui.UpdateCallbacks +import com.stripe.android.paymentelement.CustomPaymentMethodConfirmHandler +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentsheet.flowcontroller.FlowControllerFactory import com.stripe.android.utils.rememberActivity @@ -25,9 +27,11 @@ fun rememberPaymentSheetFlowController( paymentOptionCallback: PaymentOptionCallback, paymentResultCallback: PaymentSheetResultCallback, ): PaymentSheet.FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) return internalRememberPaymentSheetFlowController( paymentOptionCallback = paymentOptionCallback, paymentResultCallback = paymentResultCallback, + customPaymentMethodConfirmHandler = null, createIntentCallback = null, externalPaymentMethodConfirmHandler = null, ) @@ -50,9 +54,11 @@ fun rememberPaymentSheetFlowController( paymentOptionCallback: PaymentOptionCallback, paymentResultCallback: PaymentSheetResultCallback, ): PaymentSheet.FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) return internalRememberPaymentSheetFlowController( paymentOptionCallback = paymentOptionCallback, paymentResultCallback = paymentResultCallback, + customPaymentMethodConfirmHandler = null, createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = null, ) @@ -79,9 +85,11 @@ fun rememberPaymentSheetFlowController( paymentOptionCallback: PaymentOptionCallback, paymentResultCallback: PaymentSheetResultCallback, ): PaymentSheet.FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) return internalRememberPaymentSheetFlowController( paymentOptionCallback = paymentOptionCallback, paymentResultCallback = paymentResultCallback, + customPaymentMethodConfirmHandler = null, createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler ) @@ -140,9 +148,11 @@ private fun internalRememberPaymentSheetFlowController( } @Composable +@OptIn(ExperimentalCustomPaymentMethodsApi::class) internal fun internalRememberPaymentSheetFlowController( createIntentCallback: CreateIntentCallback?, externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler?, + customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler?, paymentOptionCallback: PaymentOptionCallback, paymentResultCallback: PaymentSheetResultCallback, ): PaymentSheet.FlowController { @@ -153,6 +163,7 @@ internal fun internalRememberPaymentSheetFlowController( val callbacks = remember(createIntentCallback, externalPaymentMethodConfirmHandler) { PaymentElementCallbacks( createIntentCallback = createIntentCallback, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, ) } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt index d1e30dcefe1..66489afa8f3 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt @@ -7,22 +7,26 @@ import androidx.activity.ComponentActivity import androidx.annotation.ColorInt import androidx.annotation.FontRes import androidx.annotation.RestrictTo +import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.fragment.app.Fragment import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi import com.stripe.android.common.configuration.ConfigurationDefaults +import com.stripe.android.core.strings.ResolvableString +import com.stripe.android.core.strings.resolvableString import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher import com.stripe.android.link.account.LinkStore import com.stripe.android.model.CardBrand import com.stripe.android.model.PaymentIntent import com.stripe.android.model.SetupIntent +import com.stripe.android.paymentelement.CustomPaymentMethodConfirmHandler +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentelement.confirmation.intent.IntentConfirmationInterceptor -import com.stripe.android.paymentsheet.PaymentSheet.Appearance.Embedded.RowStyle.FlatWithCheckmark.Colors import com.stripe.android.paymentsheet.addresselement.AddressDetails import com.stripe.android.paymentsheet.flowcontroller.FlowControllerFactory import com.stripe.android.paymentsheet.model.PaymentOption @@ -68,6 +72,7 @@ class PaymentSheet internal constructor( ) : this( DefaultPaymentSheetLauncher(activity, callback) ) { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setPaymentSheetCallbacks( createIntentCallback = null, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, @@ -90,6 +95,7 @@ class PaymentSheet internal constructor( ) : this( DefaultPaymentSheetLauncher(activity, paymentResultCallback) ) { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setPaymentSheetCallbacks( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = null, @@ -115,6 +121,7 @@ class PaymentSheet internal constructor( ) : this( DefaultPaymentSheetLauncher(activity, paymentResultCallback) ) { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setPaymentSheetCallbacks( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, @@ -149,6 +156,7 @@ class PaymentSheet internal constructor( ) : this( DefaultPaymentSheetLauncher(fragment, callback) ) { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setPaymentSheetCallbacks( createIntentCallback = null, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, @@ -171,6 +179,7 @@ class PaymentSheet internal constructor( ) : this( DefaultPaymentSheetLauncher(fragment, paymentResultCallback) ) { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setPaymentSheetCallbacks( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = null, @@ -196,6 +205,7 @@ class PaymentSheet internal constructor( ) : this( DefaultPaymentSheetLauncher(fragment, paymentResultCallback) ) { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setPaymentSheetCallbacks( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, @@ -210,6 +220,10 @@ class PaymentSheet internal constructor( class Builder(internal val resultCallback: PaymentSheetResultCallback) { internal var externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler? = null private set + + @OptIn(ExperimentalCustomPaymentMethodsApi::class) + internal var customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler? = null + private set internal var createIntentCallback: CreateIntentCallback? = null private set @@ -221,6 +235,15 @@ class PaymentSheet internal constructor( externalPaymentMethodConfirmHandler = handler } + /** + * @param handler Called when a user confirms payment for a custom payment method. Use with + * [Configuration.Builder.customPaymentMethods] to specify custom payment methods. + */ + @ExperimentalCustomPaymentMethodsApi + internal fun customPaymentMethodConfirmHandler(handler: CustomPaymentMethodConfirmHandler) = apply { + customPaymentMethodConfirmHandler = handler + } + /** * @param callback Called when the customer confirms the payment or setup. * Only used when [presentWithIntentConfiguration] is called for a deferred flow. @@ -257,16 +280,20 @@ class PaymentSheet internal constructor( /* * Callbacks are initialized & updated internally by the internal composable function */ + @OptIn(ExperimentalCustomPaymentMethodsApi::class) return internalRememberPaymentSheet( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, paymentResultCallback = resultCallback, ) } private fun initializeCallbacks() { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setPaymentSheetCallbacks( createIntentCallback = createIntentCallback, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, ) } @@ -593,6 +620,9 @@ class PaymentSheet internal constructor( internal val paymentMethodLayout: PaymentMethodLayout = ConfigurationDefaults.paymentMethodLayout, internal val cardBrandAcceptance: CardBrandAcceptance = ConfigurationDefaults.cardBrandAcceptance, + + internal val customPaymentMethods: List = + ConfigurationDefaults.customPaymentMethods, ) : Parcelable { @JvmOverloads @@ -715,6 +745,7 @@ class PaymentSheet internal constructor( preferredNetworks = preferredNetworks, allowsRemovalOfLastSavedPaymentMethod = ConfigurationDefaults.allowsRemovalOfLastSavedPaymentMethod, externalPaymentMethods = ConfigurationDefaults.externalPaymentMethods, + customPaymentMethods = ConfigurationDefaults.customPaymentMethods, ) /** @@ -744,6 +775,9 @@ class PaymentSheet internal constructor( private var paymentMethodLayout: PaymentMethodLayout = ConfigurationDefaults.paymentMethodLayout private var cardBrandAcceptance: CardBrandAcceptance = ConfigurationDefaults.cardBrandAcceptance + private var customPaymentMethods: List = + ConfigurationDefaults.customPaymentMethods + fun merchantDisplayName(merchantDisplayName: String) = apply { this.merchantDisplayName = merchantDisplayName } @@ -853,6 +887,18 @@ class PaymentSheet internal constructor( this.cardBrandAcceptance = cardBrandAcceptance } + /** + * Configuration related to custom payment methods. + * + * If set, Payment Sheet will display the defined list of custom payment methods in the UI. + */ + @ExperimentalCustomPaymentMethodsApi + internal fun customPaymentMethods( + customPaymentMethods: List, + ) = apply { + this.customPaymentMethods = customPaymentMethods + } + fun build() = Configuration( merchantDisplayName = merchantDisplayName, customer = customer, @@ -871,6 +917,7 @@ class PaymentSheet internal constructor( externalPaymentMethods = externalPaymentMethods, paymentMethodLayout = paymentMethodLayout, cardBrandAcceptance = cardBrandAcceptance, + customPaymentMethods = customPaymentMethods, ) } @@ -1936,6 +1983,77 @@ class PaymentSheet internal constructor( ) : CardBrandAcceptance() } + /** + * Defines a custom payment method type that can be displayed in Payment Element. + */ + @Poko + @Parcelize + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + class CustomPaymentMethod internal constructor( + val id: String, + internal val subtitle: ResolvableString?, + internal val disableBillingDetailCollection: Boolean = false + ) : Parcelable { + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + constructor( + /** + * The unique identifier for this custom payment method type in the format of "cmpt_...". + * + * Obtained from the Stripe Dashboard at https://dashboard.stripe.com/settings/custom_payment_methods + */ + id: String, + + /** + * Optional subtitle text to be displayed below the custom payment method's display name. + */ + @StringRes subtitle: Int?, + + /** + * When true, Payment Element will not collect billing details for this custom payment method type + * irregardless of the [PaymentSheet.Configuration.billingDetailsCollectionConfiguration] settings. + * + * This has no effect if [PaymentSheet.Configuration.billingDetailsCollectionConfiguration] is not + * configured. + */ + disableBillingDetailCollection: Boolean, + ) : this( + id = id, + subtitle = subtitle?.resolvableString, + disableBillingDetailCollection = disableBillingDetailCollection, + ) + + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + constructor( + /** + * The unique identifier for this custom payment method type in the format of "cmpt_...". + * + * Obtained from the Stripe Dashboard at https://dashboard.stripe.com/settings/custom_payment_methods + */ + id: String, + + /** + * Optional subtitle text string resource to be resolved and displayed below the custom payment method's + * display name. + */ + subtitle: String?, + + /** + * When true, Payment Element will not collect billing details for this custom payment method type + * irregardless of the [PaymentSheet.Configuration.billingDetailsCollectionConfiguration] settings. + * + * This has no effect if [PaymentSheet.Configuration.billingDetailsCollectionConfiguration] is not + * configured. + */ + disableBillingDetailCollection: Boolean, + ) : this( + id = id, + subtitle = subtitle?.resolvableString, + disableBillingDetailCollection = disableBillingDetailCollection, + ) + } + internal sealed interface CustomerAccessType : Parcelable { val analyticsValue: String @@ -2148,6 +2266,11 @@ class PaymentSheet internal constructor( ) { internal var externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler? = null private set + + @OptIn(ExperimentalCustomPaymentMethodsApi::class) + internal var customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler? = null + private set + internal var createIntentCallback: CreateIntentCallback? = null private set @@ -2158,6 +2281,15 @@ class PaymentSheet internal constructor( externalPaymentMethodConfirmHandler = handler } + /** + * @param handler Called when a user confirms payment for a custom payment method. + */ + @ExperimentalCustomPaymentMethodsApi + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun customPaymentMethodConfirmHandler(handler: CustomPaymentMethodConfirmHandler) = apply { + customPaymentMethodConfirmHandler = handler + } + /** * @param callback If specified, called when the customer confirms the payment or setup. */ @@ -2193,17 +2325,21 @@ class PaymentSheet internal constructor( /* * Callbacks are initialized & updated internally by the internal composable function */ + @OptIn(ExperimentalCustomPaymentMethodsApi::class) return internalRememberPaymentSheetFlowController( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, paymentOptionCallback = paymentOptionCallback, paymentResultCallback = resultCallback, ) } private fun initializeCallbacks() { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setFlowControllerCallbacks( createIntentCallback = createIntentCallback, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, ) } @@ -2269,6 +2405,7 @@ class PaymentSheet internal constructor( paymentOptionCallback: PaymentOptionCallback, paymentResultCallback: PaymentSheetResultCallback ): FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setFlowControllerCallbacks(externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler) return FlowControllerFactory( activity, @@ -2295,6 +2432,7 @@ class PaymentSheet internal constructor( createIntentCallback: CreateIntentCallback, paymentResultCallback: PaymentSheetResultCallback, ): FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setFlowControllerCallbacks(createIntentCallback = createIntentCallback) return FlowControllerFactory( activity, @@ -2326,6 +2464,7 @@ class PaymentSheet internal constructor( createIntentCallback: CreateIntentCallback, paymentResultCallback: PaymentSheetResultCallback, ): FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setFlowControllerCallbacks( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, @@ -2377,6 +2516,7 @@ class PaymentSheet internal constructor( paymentOptionCallback: PaymentOptionCallback, paymentResultCallback: PaymentSheetResultCallback ): FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setFlowControllerCallbacks(externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler) return FlowControllerFactory( fragment, @@ -2403,6 +2543,7 @@ class PaymentSheet internal constructor( createIntentCallback: CreateIntentCallback, paymentResultCallback: PaymentSheetResultCallback, ): FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setFlowControllerCallbacks(createIntentCallback = createIntentCallback) return FlowControllerFactory( fragment, @@ -2434,6 +2575,7 @@ class PaymentSheet internal constructor( externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler, paymentResultCallback: PaymentSheetResultCallback, ): FlowController { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) setFlowControllerCallbacks( createIntentCallback = createIntentCallback, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, @@ -2448,22 +2590,28 @@ class PaymentSheet internal constructor( } companion object { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) private fun setPaymentSheetCallbacks( createIntentCallback: CreateIntentCallback? = null, + customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler? = null, externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler? = null, ) { PaymentElementCallbackReferences[PAYMENT_SHEET_DEFAULT_CALLBACK_IDENTIFIER] = PaymentElementCallbacks( createIntentCallback = createIntentCallback, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, ) } + @OptIn(ExperimentalCustomPaymentMethodsApi::class) private fun setFlowControllerCallbacks( createIntentCallback: CreateIntentCallback? = null, + customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler? = null, externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler? = null, ) { PaymentElementCallbackReferences[FLOW_CONTROLLER_DEFAULT_CALLBACK_IDENTIFIER] = PaymentElementCallbacks( createIntentCallback = createIntentCallback, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, ) } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetCompose.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetCompose.kt index 66f94fb2807..eb88a0e9b93 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetCompose.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetCompose.kt @@ -10,6 +10,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.LocalLifecycleOwner import com.stripe.android.common.ui.UpdateCallbacks +import com.stripe.android.paymentelement.CustomPaymentMethodConfirmHandler +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.utils.rememberActivity import java.util.UUID @@ -24,11 +26,14 @@ import java.util.UUID @Composable fun rememberPaymentSheet( paymentResultCallback: PaymentSheetResultCallback, -) = internalRememberPaymentSheet( - createIntentCallback = null, - externalPaymentMethodConfirmHandler = null, - paymentResultCallback = paymentResultCallback, -) +): PaymentSheet { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) + return internalRememberPaymentSheet( + createIntentCallback = null, + externalPaymentMethodConfirmHandler = null, + paymentResultCallback = paymentResultCallback, + ) +} /** * Creates a [PaymentSheet] that is remembered across compositions. Use this method when you intend @@ -44,11 +49,14 @@ fun rememberPaymentSheet( fun rememberPaymentSheet( createIntentCallback: CreateIntentCallback, paymentResultCallback: PaymentSheetResultCallback, -) = internalRememberPaymentSheet( - createIntentCallback = createIntentCallback, - externalPaymentMethodConfirmHandler = null, - paymentResultCallback = paymentResultCallback, -) +): PaymentSheet { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) + return internalRememberPaymentSheet( + createIntentCallback = createIntentCallback, + externalPaymentMethodConfirmHandler = null, + paymentResultCallback = paymentResultCallback, + ) +} /** * Creates a [PaymentSheet] that is remembered across compositions. Use this method if you implement any external @@ -68,16 +76,21 @@ fun rememberPaymentSheet( createIntentCallback: CreateIntentCallback? = null, externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler, paymentResultCallback: PaymentSheetResultCallback, -): PaymentSheet = internalRememberPaymentSheet( - createIntentCallback = createIntentCallback, - externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, - paymentResultCallback = paymentResultCallback, -) +): PaymentSheet { + @OptIn(ExperimentalCustomPaymentMethodsApi::class) + return internalRememberPaymentSheet( + createIntentCallback = createIntentCallback, + externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, + paymentResultCallback = paymentResultCallback, + ) +} @Composable +@OptIn(ExperimentalCustomPaymentMethodsApi::class) internal fun internalRememberPaymentSheet( createIntentCallback: CreateIntentCallback? = null, externalPaymentMethodConfirmHandler: ExternalPaymentMethodConfirmHandler? = null, + customPaymentMethodConfirmHandler: CustomPaymentMethodConfirmHandler? = null, paymentResultCallback: PaymentSheetResultCallback, ): PaymentSheet { val paymentElementCallbackIdentifier = rememberSaveable { @@ -87,6 +100,7 @@ internal fun internalRememberPaymentSheet( val callbacks = remember(createIntentCallback, externalPaymentMethodConfirmHandler) { PaymentElementCallbacks( createIntentCallback = createIntentCallback, + customPaymentMethodConfirmHandler = customPaymentMethodConfirmHandler, externalPaymentMethodConfirmHandler = externalPaymentMethodConfirmHandler, ) } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbackReferencesTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbackReferencesTest.kt index 86e1beed6b9..debea5f3457 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbackReferencesTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/callbacks/PaymentElementCallbackReferencesTest.kt @@ -1,6 +1,7 @@ package com.stripe.android.paymentelement.callbacks import com.google.common.truth.Truth.assertThat +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.utils.PaymentElementCallbackTestRule import org.junit.Rule import org.junit.Test @@ -52,11 +53,15 @@ class PaymentElementCallbackReferencesTest { assertThat(PaymentElementCallbackReferences[DEFAULT_TEST_KEY]).isNull() } + @OptIn(ExperimentalCustomPaymentMethodsApi::class) private fun createCallbacks(): PaymentElementCallbacks { return PaymentElementCallbacks( createIntentCallback = { _, _ -> error("Should not be called!") }, + customPaymentMethodConfirmHandler = { _, _ -> + error("Should not be called!") + }, externalPaymentMethodConfirmHandler = { _, _ -> error("Should not be called!") } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/epms/ExternalPaymentMethodConfirmationActivityTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/epms/ExternalPaymentMethodConfirmationActivityTest.kt index 8adb2afbd10..d15efeba3fb 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/epms/ExternalPaymentMethodConfirmationActivityTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/epms/ExternalPaymentMethodConfirmationActivityTest.kt @@ -16,6 +16,7 @@ import com.stripe.android.core.exception.LocalStripeException import com.stripe.android.core.strings.resolvableString import com.stripe.android.isInstanceOf import com.stripe.android.model.PaymentMethod +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentelement.confirmation.ConfirmationHandler @@ -45,6 +46,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @RunWith(RobolectricTestRunner::class) +@OptIn(ExperimentalCustomPaymentMethodsApi::class) internal class ExternalPaymentMethodConfirmationActivityTest { private val application = ApplicationProvider.getApplicationContext() @@ -61,6 +63,7 @@ internal class ExternalPaymentMethodConfirmationActivityTest { fun setup() { PaymentElementCallbackReferences["ConfirmationTestIdentifier"] = PaymentElementCallbacks( createIntentCallback = null, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = { _, _ -> error("Should not be called!") } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/CreateIntentFactory.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/CreateIntentFactory.kt index b4c553c6868..d9702238c11 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/CreateIntentFactory.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/CreateIntentFactory.kt @@ -2,6 +2,7 @@ package com.stripe.android.paymentelement.confirmation.lpms.foundations import com.stripe.android.model.PaymentMethod import com.stripe.android.model.StripeIntent +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentelement.confirmation.lpms.foundations.network.MerchantCountry @@ -12,6 +13,7 @@ import com.stripe.android.paymentsheet.state.PaymentElementLoader import com.stripe.android.testing.PaymentIntentFactory import com.stripe.android.testing.SetupIntentFactory +@OptIn(ExperimentalCustomPaymentMethodsApi::class) internal class CreateIntentFactory( private val paymentElementCallbackIdentifier: String, private val paymentMethodType: PaymentMethod.Type, @@ -68,6 +70,7 @@ internal class CreateIntentFactory( } ) }, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = null, ) ) @@ -130,6 +133,7 @@ internal class CreateIntentFactory( } ) }, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = null, ) ) diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/content/EmbeddedPaymentElementInitializerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/content/EmbeddedPaymentElementInitializerTest.kt index ccdb7a2038a..a07a34c09e4 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/content/EmbeddedPaymentElementInitializerTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/content/EmbeddedPaymentElementInitializerTest.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.testing.TestLifecycleOwner import com.google.common.truth.Truth.assertThat +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentelement.embedded.FakeEmbeddedSheetLauncher @@ -35,12 +36,16 @@ internal class EmbeddedPaymentElementInitializerTest { } @Test + @OptIn(ExperimentalCustomPaymentMethodsApi::class) fun `when lifecycle is destroyed, should un-initialize callbacks`() { val owner = TestLifecycleOwner() val callbacks = PaymentElementCallbacks( createIntentCallback = { _, _ -> error("Not implemented") }, + customPaymentMethodConfirmHandler = { _, _ -> + error("Not implemented") + }, externalPaymentMethodConfirmHandler = { _, _ -> error("Not implemented") } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncherTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncherTest.kt index bd5c46149f7..7af68487b3e 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncherTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncherTest.kt @@ -11,6 +11,7 @@ import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import com.stripe.android.ApiKeyFixtures import com.stripe.android.PaymentConfiguration +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentsheet.state.PaymentElementLoader @@ -21,6 +22,7 @@ import kotlin.test.BeforeTest import kotlin.test.Test @RunWith(RobolectricTestRunner::class) +@OptIn(ExperimentalCustomPaymentMethodsApi::class) class DefaultPaymentSheetLauncherTest { @BeforeTest @@ -90,6 +92,7 @@ class DefaultPaymentSheetLauncherTest { createIntentCallback = { _, _ -> error("I’m alive") }, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = null, ) @@ -117,6 +120,7 @@ class DefaultPaymentSheetLauncherTest { fun `Clears out externalPaymentMethodConfirmHandler when lifecycle owner is destroyed`() { PaymentElementCallbackReferences[PAYMENT_SHEET_DEFAULT_CALLBACK_IDENTIFIER] = PaymentElementCallbacks( createIntentCallback = null, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = { _, _ -> error("I’m alive") }, @@ -151,6 +155,45 @@ class DefaultPaymentSheetLauncherTest { ).isNull() } + @Test + fun `Clears out customPaymentMethodConfirmHandler when lifecycle owner is destroyed`() { + PaymentElementCallbackReferences[PAYMENT_SHEET_DEFAULT_CALLBACK_IDENTIFIER] = PaymentElementCallbacks( + createIntentCallback = null, + customPaymentMethodConfirmHandler = { _, _ -> + error("I’m alive") + }, + externalPaymentMethodConfirmHandler = null, + ) + + val lifecycleOwner = TestLifecycleOwner() + + DefaultPaymentSheetLauncher( + activityResultLauncher = mock(), + activity = mock(), + lifecycleOwner = lifecycleOwner, + application = ApplicationProvider.getApplicationContext(), + callback = mock(), + ) + + lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) + assertThat( + PaymentElementCallbackReferences[PAYMENT_SHEET_DEFAULT_CALLBACK_IDENTIFIER] + ?.customPaymentMethodConfirmHandler + ).isNotNull() + + lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + assertThat( + PaymentElementCallbackReferences[PAYMENT_SHEET_DEFAULT_CALLBACK_IDENTIFIER] + ?.customPaymentMethodConfirmHandler + ).isNotNull() + + lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + assertThat( + PaymentElementCallbackReferences[PAYMENT_SHEET_DEFAULT_CALLBACK_IDENTIFIER] + ?.customPaymentMethodConfirmHandler + ).isNull() + } + private class FakeActivityResultRegistry( private val result: PaymentSheetResult? = null, private val error: Throwable? = null diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/ExternalPaymentMethodProxyActivityTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/ExternalPaymentMethodProxyActivityTest.kt index a36f48b6567..078c69814b3 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/ExternalPaymentMethodProxyActivityTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/ExternalPaymentMethodProxyActivityTest.kt @@ -11,6 +11,7 @@ import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import com.stripe.android.model.Address import com.stripe.android.model.PaymentMethod +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.testing.FakeErrorReporter @@ -22,6 +23,7 @@ import kotlin.test.fail @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) +@OptIn(ExperimentalCustomPaymentMethodsApi::class) class ExternalPaymentMethodProxyActivityTest { private val context: Context = ApplicationProvider.getApplicationContext() @@ -38,6 +40,7 @@ class ExternalPaymentMethodProxyActivityTest { PaymentElementCallbackReferences["ExternalPaymentMethod"] = PaymentElementCallbacks( createIntentCallback = null, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = confirmHandler, ) @@ -67,6 +70,7 @@ class ExternalPaymentMethodProxyActivityTest { PaymentElementCallbackReferences["ExternalPaymentMethod"] = PaymentElementCallbacks( createIntentCallback = null, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = confirmHandler, ) @@ -139,11 +143,13 @@ class ExternalPaymentMethodProxyActivityTest { PaymentElementCallbackReferences["ExternalPaymentMethodTestIdentifierOne"] = PaymentElementCallbacks( createIntentCallback = null, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = firstConfirmHandler, ) PaymentElementCallbackReferences["ExternalPaymentMethodTestIdentifierTwo"] = PaymentElementCallbacks( createIntentCallback = null, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = secondConfirmHandler, ) diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt index 3d0342628cd..384f9d8a278 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt @@ -56,6 +56,7 @@ import com.stripe.android.model.PaymentMethodOptionsParams import com.stripe.android.model.PaymentMethodUpdateParams import com.stripe.android.model.SetupIntentFixtures import com.stripe.android.model.StripeIntent +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentelement.confirmation.ConfirmationDefinition @@ -173,6 +174,7 @@ import com.stripe.android.R as PaymentsCoreR @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) +@OptIn(ExperimentalCustomPaymentMethodsApi::class) internal class PaymentSheetViewModelTest { private val testDispatcher = UnconfinedTestDispatcher() @@ -2126,6 +2128,9 @@ internal class PaymentSheetViewModelTest { createIntentCallback = { _, _ -> error("Should not be called!") }, + customPaymentMethodConfirmHandler = { _, _ -> + error("Should not be called!") + }, externalPaymentMethodConfirmHandler = { _, _ -> error("Should not be called!") }, @@ -2145,6 +2150,9 @@ internal class PaymentSheetViewModelTest { createIntentCallback = { _, _ -> error("Should not be called!") }, + customPaymentMethodConfirmHandler = { _, _ -> + error("Should not be called!") + }, externalPaymentMethodConfirmHandler = { _, _ -> error("Should not be called!") }, diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt index 63cfe76045d..0fcdaa82289 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt @@ -36,6 +36,7 @@ import com.stripe.android.model.PaymentMethodFixtures import com.stripe.android.model.PaymentMethodOptionsParams import com.stripe.android.model.StripeIntent import com.stripe.android.model.wallets.Wallet +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentelement.confirmation.createTestConfirmationHandlerFactory @@ -119,6 +120,7 @@ import kotlin.test.Test @Suppress("DEPRECATION") @RunWith(RobolectricTestRunner::class) +@OptIn(ExperimentalCustomPaymentMethodsApi::class) internal class DefaultFlowControllerTest { @get:Rule @@ -1466,6 +1468,9 @@ internal class DefaultFlowControllerTest { createIntentCallback = { _, _ -> error("Should not be called!") }, + customPaymentMethodConfirmHandler = { _, _ -> + error("Should not be called!") + }, externalPaymentMethodConfirmHandler = { _, _ -> error("Should not be called!") }, @@ -1906,6 +1911,7 @@ internal class DefaultFlowControllerTest { createIntentCallback = { _, _ -> error("I’m alive") }, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = null, ) @@ -1934,6 +1940,7 @@ internal class DefaultFlowControllerTest { fun `Clears out externalPaymentMethodConfirmHandler when lifecycle owner is destroyed`() { PaymentElementCallbackReferences[FLOW_CONTROLLER_CALLBACK_TEST_IDENTIFIER] = PaymentElementCallbacks( createIntentCallback = null, + customPaymentMethodConfirmHandler = null, externalPaymentMethodConfirmHandler = { _, _ -> error("I’m alive") } @@ -1960,6 +1967,37 @@ internal class DefaultFlowControllerTest { ).isNull() } + @Test + fun `Clears out customPaymentMethodConfirmHandler when lifecycle owner is destroyed`() { + PaymentElementCallbackReferences[FLOW_CONTROLLER_CALLBACK_TEST_IDENTIFIER] = PaymentElementCallbacks( + createIntentCallback = null, + customPaymentMethodConfirmHandler = { _, _ -> + error("Should not be called!") + }, + externalPaymentMethodConfirmHandler = null + ) + + createFlowController() + + lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) + assertThat( + PaymentElementCallbackReferences[FLOW_CONTROLLER_CALLBACK_TEST_IDENTIFIER] + ?.customPaymentMethodConfirmHandler + ).isNotNull() + + lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + assertThat( + PaymentElementCallbackReferences[FLOW_CONTROLLER_CALLBACK_TEST_IDENTIFIER] + ?.customPaymentMethodConfirmHandler + ).isNotNull() + + lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + assertThat( + PaymentElementCallbackReferences[FLOW_CONTROLLER_CALLBACK_TEST_IDENTIFIER] + ?.customPaymentMethodConfirmHandler + ).isNull() + } + @Test fun `On external payment error, should report external payment method failure`() = runTest { val eventReporter = FakeEventReporter() diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt index 9f99791d7f6..acdffe1b266 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt @@ -9,6 +9,7 @@ import com.stripe.android.ApiKeyFixtures import com.stripe.android.PaymentConfiguration import com.stripe.android.core.networking.AnalyticsRequestFactory import com.stripe.android.model.PaymentIntentFixtures +import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackReferences import com.stripe.android.paymentelement.callbacks.PaymentElementCallbacks import com.stripe.android.paymentsheet.FLOW_CONTROLLER_DEFAULT_CALLBACK_IDENTIFIER @@ -43,6 +44,7 @@ import org.robolectric.RobolectricTestRunner import kotlin.time.Duration.Companion.seconds @RunWith(RobolectricTestRunner::class) +@OptIn(ExperimentalCustomPaymentMethodsApi::class) class FlowControllerConfigurationHandlerTest { @get:Rule @@ -454,6 +456,9 @@ class FlowControllerConfigurationHandlerTest { createIntentCallback = { _, _ -> error("Should not be called!") }, + customPaymentMethodConfirmHandler = { _, _ -> + error("Should not be called!") + }, externalPaymentMethodConfirmHandler = { _, _ -> error("Should not be called!") }, @@ -492,6 +497,9 @@ class FlowControllerConfigurationHandlerTest { createIntentCallback = { _, _ -> error("Should not be called!") }, + customPaymentMethodConfirmHandler = { _, _ -> + error("Should not be called!") + }, externalPaymentMethodConfirmHandler = { _, _ -> error("Should not be called!") },