diff --git a/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt b/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt index 48b9ba2578a..9eb00e227eb 100644 --- a/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt +++ b/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt @@ -8,6 +8,13 @@ import com.stripe.android.core.ApiKeyValidator import dev.drewhamilton.poko.Poko import kotlinx.parcelize.Parcelize +/** + * Creates a [PaymentConfiguration] for per-instance use (e.g. passing to + * [EmbeddedPaymentElement.Configuration.Builder.paymentConfiguration]). + * + * Unlike [PaymentConfiguration.init], this constructor does not write to SharedPreferences + * and does not affect the global singleton returned by [PaymentConfiguration.getInstance]. + */ @Parcelize @Poko class PaymentConfiguration @@ -17,6 +24,20 @@ constructor( val stripeAccountId: String? = null ) : Parcelable { + class Builder(private val publishableKey: String) { + internal var stripeAccountId: String? = null + private set + + fun stripeAccountId(stripeAccountId: String?) = apply { + this.stripeAccountId = stripeAccountId + } + + fun build() = PaymentConfiguration( + publishableKey = publishableKey, + stripeAccountId = stripeAccountId + ) + } + init { ApiKeyValidator.get().requireValid(publishableKey) } diff --git a/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationHolder.kt b/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationHolder.kt new file mode 100644 index 00000000000..0676ddff941 --- /dev/null +++ b/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationHolder.kt @@ -0,0 +1,25 @@ +package com.stripe.android.payments.core.injection + +import android.content.Context +import com.stripe.android.PaymentConfiguration +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Mutable holder that provides the [PaymentConfiguration] for a single Dagger component scope. + * + * When [paymentConfiguration] is null (the default), [get] falls back to + * [PaymentConfiguration.getInstance] — the same global singleton the SDK has always used. + * Setting a non-null value overrides the singleton for all network requests bound to this + * component (i.e., this [EmbeddedPaymentElement] instance). + */ +@Singleton +class PaymentConfigurationHolder @Inject constructor( + private val context: Context, +) { + @Volatile + var paymentConfiguration: PaymentConfiguration? = null + + fun get(): PaymentConfiguration = + paymentConfiguration ?: PaymentConfiguration.getInstance(context) +} diff --git a/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationModule.kt b/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationModule.kt index c7f29e2d2f9..a232041695a 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationModule.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationModule.kt @@ -1,6 +1,5 @@ package com.stripe.android.payments.core.injection -import android.content.Context import androidx.annotation.RestrictTo import com.stripe.android.PaymentConfiguration import com.stripe.android.core.injection.PUBLISHABLE_KEY @@ -14,8 +13,10 @@ import javax.inject.Provider @Module class PaymentConfigurationModule { @Provides - fun providePaymentConfiguration(context: Context): PaymentConfiguration { - return PaymentConfiguration.getInstance(context) + fun providePaymentConfiguration( + holder: PaymentConfigurationHolder, + ): PaymentConfiguration { + return holder.get() } @Provides 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 aecf725d253..a97b7ba3d76 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElement.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/EmbeddedPaymentElement.kt @@ -15,7 +15,9 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStoreOwner import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi +import com.stripe.android.PaymentConfiguration import com.stripe.android.SharedPaymentTokenSessionPreview +import com.stripe.android.payments.core.injection.PaymentConfigurationHolder import com.stripe.android.checkout.Checkout import com.stripe.android.checkout.CheckoutConfigurationMerger import com.stripe.android.checkout.CheckoutInstances @@ -59,6 +61,7 @@ class EmbeddedPaymentElement @Inject internal constructor( paymentOptionDisplayDataHolder: PaymentOptionDisplayDataHolder, private val configurationCoordinator: EmbeddedConfigurationCoordinator, stateHelper: EmbeddedStateHelper, + private val paymentConfigurationHolder: PaymentConfigurationHolder, ) { /** @@ -85,6 +88,7 @@ class EmbeddedPaymentElement @Inject internal constructor( intentConfiguration: PaymentSheet.IntentConfiguration, configuration: Configuration, ): ConfigureResult { + paymentConfigurationHolder.paymentConfiguration = configuration.paymentConfiguration val initializationMode = PaymentElementLoader.InitializationMode.DeferredIntent(intentConfiguration) return configurationCoordinator.configure(configuration, initializationMode) } @@ -102,6 +106,7 @@ class EmbeddedPaymentElement @Inject internal constructor( checkout: Checkout, configuration: Configuration, ): ConfigureResult { + paymentConfigurationHolder.paymentConfiguration = configuration.paymentConfiguration CheckoutInstances.ensureNoMutationInFlight(checkout.internalState.key) return configurationCoordinator.configure( configuration = CheckoutConfigurationMerger.EmbeddedConfiguration(configuration) @@ -305,6 +310,7 @@ class EmbeddedPaymentElement @Inject internal constructor( internal val termsDisplay: Map = emptyMap(), internal val opensCardScannerAutomatically: Boolean = ConfigurationDefaults.opensCardScannerAutomatically, internal val userOverrideCountry: String? = ConfigurationDefaults.userOverrideCountry, + internal val paymentConfiguration: PaymentConfiguration? = null, ) : Parcelable { @Suppress("TooManyFunctions") class Builder( @@ -342,6 +348,7 @@ class EmbeddedPaymentElement @Inject internal constructor( private var opensCardScannerAutomatically: Boolean = ConfigurationDefaults.opensCardScannerAutomatically private var userOverrideCountry: String? = ConfigurationDefaults.userOverrideCountry + private var paymentConfiguration: PaymentConfiguration? = null /** * If set, the customer can select a previously saved payment method. @@ -578,6 +585,16 @@ class EmbeddedPaymentElement @Inject internal constructor( this.userOverrideCountry = userOverrideCountry } + /** + * An optional [PaymentConfiguration] for this payment session. When set, overrides the + * global [PaymentConfiguration] singleton for all network requests made by this + * [EmbeddedPaymentElement] instance. When not set, defaults to the value from + * [PaymentConfiguration.getInstance]. + */ + fun paymentConfiguration(paymentConfiguration: PaymentConfiguration) = apply { + this.paymentConfiguration = paymentConfiguration + } + fun build() = Configuration( merchantDisplayName = merchantDisplayName, customer = customer, @@ -602,6 +619,7 @@ class EmbeddedPaymentElement @Inject internal constructor( termsDisplay = termsDisplay, opensCardScannerAutomatically = opensCardScannerAutomatically, userOverrideCountry = userOverrideCountry, + paymentConfiguration = paymentConfiguration, ) } @@ -633,6 +651,7 @@ class EmbeddedPaymentElement @Inject internal constructor( .userOverrideCountry(userOverrideCountry) .apply { primaryButtonLabel?.let { primaryButtonLabel(it) } + paymentConfiguration?.let { paymentConfiguration(it) } } } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/content/EmbeddedConfigurationHandler.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/content/EmbeddedConfigurationHandler.kt index d8348c2baef..c4d515b8d9d 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/content/EmbeddedConfigurationHandler.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/content/EmbeddedConfigurationHandler.kt @@ -112,6 +112,10 @@ internal class DefaultEmbeddedConfigurationHandler @Inject constructor( return coalescingOrchestrator.get() } + // TODO: paymentConfiguration is not included in the Arguments cache key (CommonConfiguration + // doesn't carry it). Calling configure() twice with the same initializationMode/configuration + // but a different PaymentConfiguration will return the cached result without re-loading. + // Fix by adding paymentConfiguration: PaymentConfiguration? to Arguments. @Parcelize data class Arguments( val initializationMode: PaymentElementLoader.InitializationMode,