diff --git a/payments-core/api/payments-core.api b/payments-core/api/payments-core.api index 134bf47d0e2..246f016863f 100644 --- a/payments-core/api/payments-core.api +++ b/payments-core/api/payments-core.api @@ -4588,6 +4588,36 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$GooglePayConfigu public static fun values ()[Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration$Environment; } +public final class com/stripe/android/paymentsheet/PaymentSheetContract : androidx/activity/result/contract/ActivityResultContract { + public static final field EXTRA_ARGS Ljava/lang/String; + public fun ()V + public fun createIntent (Landroid/content/Context;Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args;)Landroid/content/Intent; + public synthetic fun createIntent (Landroid/content/Context;Ljava/lang/Object;)Landroid/content/Intent; + public fun parseResult (ILandroid/content/Intent;)Lcom/stripe/android/paymentsheet/PaymentSheetResult; + public synthetic fun parseResult (ILandroid/content/Intent;)Ljava/lang/Object; +} + +public final class com/stripe/android/paymentsheet/PaymentSheetContract$Args : com/stripe/android/view/ActivityStarter$Args { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public static final field Companion Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args$Companion; + public final fun copy (Lcom/stripe/android/paymentsheet/model/ClientSecret;Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;Ljava/lang/Integer;)Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args; + public static synthetic fun copy$default (Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args;Lcom/stripe/android/paymentsheet/model/ClientSecret;Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getGooglePayConfig ()Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration; + public fun hashCode ()I + public final fun isGooglePayEnabled ()Z + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class com/stripe/android/paymentsheet/PaymentSheetContract$Args$Companion { + public final fun createPaymentIntentArgs (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;)Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args; + public static synthetic fun createPaymentIntentArgs$default (Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args$Companion;Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args; + public final fun createSetupIntentArgs (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;)Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args; + public static synthetic fun createSetupIntentArgs$default (Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args$Companion;Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheetContract$Args; +} + public abstract class com/stripe/android/paymentsheet/PaymentSheetResult : android/os/Parcelable { } diff --git a/payments-core/src/main/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncher.kt b/payments-core/src/main/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncher.kt index a509cb7545a..d6f9d71cd34 100644 --- a/payments-core/src/main/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncher.kt +++ b/payments-core/src/main/java/com/stripe/android/paymentsheet/DefaultPaymentSheetLauncher.kt @@ -1,17 +1,17 @@ package com.stripe.android.paymentsheet -import android.app.Activity import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultRegistry import androidx.fragment.app.Fragment -import com.stripe.android.paymentsheet.model.PaymentIntentClientSecret -import com.stripe.android.paymentsheet.model.SetupIntentClientSecret import org.jetbrains.annotations.TestOnly +/** + * This is used internally for integrations that don't use Jetpack Compose and are + * able to pass in an activity. + */ internal class DefaultPaymentSheetLauncher( private val activityResultLauncher: ActivityResultLauncher, - private val statusBarColor: () -> Int? ) : PaymentSheetLauncher { constructor( @@ -22,11 +22,7 @@ internal class DefaultPaymentSheetLauncher( PaymentSheetContract() ) { callback.onPaymentSheetResult(it) - }, - - // lazily access the statusBarColor in case the value changes between when this - // class is instantiated and the payment sheet is launched - { getStatusBarColor(activity) } + } ) constructor( @@ -37,11 +33,7 @@ internal class DefaultPaymentSheetLauncher( PaymentSheetContract() ) { callback.onPaymentSheetResult(it) - }, - - // lazily access the statusBarColor in case the value changes between when this - // class is instantiated and the payment sheet is launched - { getStatusBarColor(fragment.activity) } + } ) @TestOnly @@ -55,20 +47,15 @@ internal class DefaultPaymentSheetLauncher( registry ) { callback.onPaymentSheetResult(it) - }, - - // lazily access the statusBarColor in case the value changes between when this - // class is instantiated and the payment sheet is launched - { getStatusBarColor(fragment.activity) } + } ) override fun presentWithPaymentIntent( paymentIntentClientSecret: String, configuration: PaymentSheet.Configuration? ) = present( - PaymentSheetContract.Args( - PaymentIntentClientSecret(paymentIntentClientSecret), - statusBarColor(), + PaymentSheetContract.Args.createPaymentIntentArgs( + paymentIntentClientSecret, configuration ) ) @@ -77,9 +64,8 @@ internal class DefaultPaymentSheetLauncher( setupIntentClientSecret: String, configuration: PaymentSheet.Configuration? ) = present( - PaymentSheetContract.Args( - SetupIntentClientSecret(setupIntentClientSecret), - statusBarColor(), + PaymentSheetContract.Args.createSetupIntentArgs( + setupIntentClientSecret, configuration ) ) @@ -87,8 +73,4 @@ internal class DefaultPaymentSheetLauncher( private fun present(args: PaymentSheetContract.Args) { activityResultLauncher.launch(args) } - - private companion object { - private fun getStatusBarColor(activity: Activity?) = activity?.window?.statusBarColor - } } diff --git a/payments-core/src/main/java/com/stripe/android/paymentsheet/PaymentSheetContract.kt b/payments-core/src/main/java/com/stripe/android/paymentsheet/PaymentSheetContract.kt index c39120e014f..a56081b54c3 100644 --- a/payments-core/src/main/java/com/stripe/android/paymentsheet/PaymentSheetContract.kt +++ b/payments-core/src/main/java/com/stripe/android/paymentsheet/PaymentSheetContract.kt @@ -1,24 +1,27 @@ package com.stripe.android.paymentsheet +import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle import androidx.activity.result.contract.ActivityResultContract import androidx.annotation.ColorInt +import androidx.annotation.VisibleForTesting import androidx.core.os.bundleOf import com.stripe.android.paymentsheet.model.ClientSecret import com.stripe.android.paymentsheet.model.PaymentIntentClientSecret import com.stripe.android.view.ActivityStarter import kotlinx.parcelize.Parcelize -internal class PaymentSheetContract : +class PaymentSheetContract : ActivityResultContract() { override fun createIntent( context: Context, input: Args ): Intent { + val statusBarColor = (context as? Activity)?.window?.statusBarColor return Intent(context, PaymentSheetActivity::class.java) - .putExtra(EXTRA_ARGS, input) + .putExtra(EXTRA_ARGS, input.copy(statusBarColor = statusBarColor)) } override fun parseResult( @@ -32,19 +35,35 @@ internal class PaymentSheetContract : } @Parcelize - internal data class Args( - val clientSecret: ClientSecret, - @ColorInt val statusBarColor: Int?, - val config: PaymentSheet.Configuration? + data class Args @VisibleForTesting internal constructor( + internal val clientSecret: ClientSecret, + internal val config: PaymentSheet.Configuration?, + @ColorInt internal val statusBarColor: Int? = null, ) : ActivityStarter.Args { val googlePayConfig: PaymentSheet.GooglePayConfiguration? get() = config?.googlePay val isGooglePayEnabled: Boolean get() = googlePayConfig != null && clientSecret is PaymentIntentClientSecret - internal companion object { + companion object { internal fun fromIntent(intent: Intent): Args? { return intent.getParcelableExtra(EXTRA_ARGS) } + + fun createPaymentIntentArgs( + clientSecret: String, + config: PaymentSheet.Configuration? = null + ) = Args( + PaymentIntentClientSecret(clientSecret), + config + ) + + fun createSetupIntentArgs( + clientSecret: String, + config: PaymentSheet.Configuration? = null + ) = Args( + PaymentIntentClientSecret(clientSecret), + config + ) } } @@ -57,8 +76,10 @@ internal class PaymentSheetContract : } } - private companion object { - private const val EXTRA_ARGS = + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal companion object { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + const val EXTRA_ARGS = "com.stripe.android.paymentsheet.PaymentSheetContract.extra_args" private const val EXTRA_RESULT = "com.stripe.android.paymentsheet.PaymentSheetContract.extra_result" diff --git a/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt b/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt index 113d776f08f..152ebd25137 100644 --- a/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt +++ b/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt @@ -41,6 +41,7 @@ import com.stripe.android.utils.InjectableActivityScenario import com.stripe.android.utils.TestUtils.idleLooper import com.stripe.android.utils.TestUtils.viewModelFactoryFor import com.stripe.android.utils.injectableActivityScenario +import com.stripe.android.view.ActivityScenarioFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking @@ -78,8 +79,8 @@ internal class PaymentSheetActivityTest { context, PaymentSheetContract.Args( PaymentIntentClientSecret("client_secret"), + PaymentSheetFixtures.CONFIG_CUSTOMER, statusBarColor = PaymentSheetFixtures.STATUS_BAR_COLOR, - PaymentSheetFixtures.CONFIG_CUSTOMER ) ) @@ -595,13 +596,24 @@ internal class PaymentSheetActivityTest { @Test fun `sets expected statusBarColor`() { - val scenario = activityScenario() - scenario.launch(intent).use { - it.onActivity { activity -> - assertThat(activity.window.statusBarColor) - .isEqualTo(PaymentSheetFixtures.STATUS_BAR_COLOR) - scenario.moveToState(Lifecycle.State.DESTROYED) - } + val activityScenarioFactory = ActivityScenarioFactory(context) + val activityScenario = activityScenarioFactory.createAddPaymentMethodActivity() + activityScenario.moveToState(Lifecycle.State.CREATED) + activityScenario.onActivity { activity -> + activity.window.statusBarColor = PaymentSheetFixtures.STATUS_BAR_COLOR + + val intent = contract.createIntent( + activity, + PaymentSheetContract.Args( + PaymentIntentClientSecret("client_secret"), + PaymentSheetFixtures.CONFIG_CUSTOMER + ) + ) + + val args = + intent.extras?.get(PaymentSheetContract.EXTRA_ARGS) as PaymentSheetContract.Args + assertThat(args.statusBarColor) + .isEqualTo(PaymentSheetFixtures.STATUS_BAR_COLOR) } } diff --git a/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt b/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt index 2a871ff4b2d..87a4173eaa5 100644 --- a/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt +++ b/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt @@ -37,25 +37,25 @@ internal object PaymentSheetFixtures { internal val ARGS_CUSTOMER_WITH_GOOGLEPAY_SETUP = PaymentSheetContract.Args( SetupIntentClientSecret(CLIENT_SECRET), + CONFIG_CUSTOMER_WITH_GOOGLEPAY, STATUS_BAR_COLOR, - CONFIG_CUSTOMER_WITH_GOOGLEPAY ) internal val ARGS_CUSTOMER_WITH_GOOGLEPAY = PaymentSheetContract.Args( PAYMENT_INTENT_CLIENT_SECRET, + CONFIG_CUSTOMER_WITH_GOOGLEPAY, STATUS_BAR_COLOR, - CONFIG_CUSTOMER_WITH_GOOGLEPAY ) internal val ARGS_CUSTOMER_WITHOUT_GOOGLEPAY = PaymentSheetContract.Args( PAYMENT_INTENT_CLIENT_SECRET, + CONFIG_CUSTOMER, STATUS_BAR_COLOR, - CONFIG_CUSTOMER ) internal val ARGS_WITHOUT_CUSTOMER = PaymentSheetContract.Args( PAYMENT_INTENT_CLIENT_SECRET, + config = null, STATUS_BAR_COLOR, - config = null ) } diff --git a/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt b/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt index 5cbe0ce40fd..dbad3eb217d 100644 --- a/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt +++ b/payments-core/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt @@ -19,7 +19,6 @@ import com.stripe.android.model.PaymentIntentFixtures import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethodCreateParamsFixtures import com.stripe.android.model.PaymentMethodFixtures -import com.stripe.android.model.SetupIntentFixtures import com.stripe.android.model.StripeIntent import com.stripe.android.networking.AbsFakeStripeRepository import com.stripe.android.networking.ApiRequest @@ -689,7 +688,6 @@ internal class PaymentSheetViewModelTest { private val PAYMENT_METHODS = listOf(PaymentMethodFixtures.CARD_PAYMENT_METHOD) - val SETUP_INTENT = SetupIntentFixtures.SI_REQUIRES_PAYMENT_METHOD val PAYMENT_INTENT = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD val PAYMENT_INTENT_RESULT = PaymentIntentResult( intent = PAYMENT_INTENT, diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/activity/LaunchPaymentSheetWithComposeActivity.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/activity/LaunchPaymentSheetWithComposeActivity.kt new file mode 100644 index 00000000000..dc8e211045a --- /dev/null +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/activity/LaunchPaymentSheetWithComposeActivity.kt @@ -0,0 +1,57 @@ +package com.stripe.android.paymentsheet.example.activity + +import android.os.Bundle +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.platform.LocalContext +import com.stripe.android.paymentsheet.PaymentSheet +import com.stripe.android.paymentsheet.PaymentSheetContract + +internal class LaunchPaymentSheetWithComposeActivity : + BasePaymentSheetActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MaterialTheme { + val stripeLauncher = + rememberLauncherForActivityResult(contract = PaymentSheetContract()) + { + onPaymentSheetResult(it) + } + + val inProgress by viewModel.inProgress.observeAsState(false) + val status by viewModel.status.observeAsState("") + + if (status.isNotBlank()) { + Toast.makeText(LocalContext.current, status, Toast.LENGTH_SHORT).show() + viewModel.statusDisplayed() + } + + Receipt(inProgress) { + BuyButton( + buyButtonEnabled = !inProgress, + onClick = { + prepareCheckout { customerConfig, clientSecret -> + stripeLauncher.launch( + PaymentSheetContract.Args.createPaymentIntentArgs( + clientSecret, + PaymentSheet.Configuration( + merchantDisplayName = merchantName, + customer = customerConfig, + googlePay = googlePayConfig, + ) + ) + ) + } + } + ) + } + } + } + } +}