From ddbba63c4ed414e67a446d8ad37b223188913ecc Mon Sep 17 00:00:00 2001 From: tjclawson Date: Wed, 1 Jul 2026 19:27:10 +0000 Subject: [PATCH 1/2] Refactor PaymentConfiguration for per-instance use in EmbeddedPaymentElement - Remove @RestrictTo from PaymentConfiguration constructor so users can construct it directly for per-instance use. Add KDoc explaining that direct construction does not write to SharedPreferences or affect the global singleton returned by getInstance(). - Add PaymentConfigurationHolder: a mutable, @Singleton-scoped holder that owns a nullable PaymentConfiguration override. When the override is null (the default), get() falls back to PaymentConfiguration.getInstance(context), preserving existing behavior for all non-embedded components. - Update PaymentConfigurationModule.providePaymentConfiguration to delegate to PaymentConfigurationHolder.get() instead of calling getInstance(context) directly. Remove unused Context import. - Wire PaymentConfiguration through EmbeddedPaymentElement.Configuration: add an optional paymentConfiguration field (default null) to the data class and Builder, and set the holder in both configure() overloads so per-instance overrides take effect before loading begins. - Add TODO comment in EmbeddedConfigurationHandler noting that the cache key (Arguments) does not include paymentConfiguration, so calling configure() twice with the same intent/config but a different PaymentConfiguration returns the cached result. Committed-By-Agent: goose --- .../stripe/android/PaymentConfiguration.kt | 11 +++++--- .../injection/PaymentConfigurationHolder.kt | 25 +++++++++++++++++++ .../injection/PaymentConfigurationModule.kt | 7 +++--- .../paymentelement/EmbeddedPaymentElement.kt | 19 ++++++++++++++ .../content/EmbeddedConfigurationHandler.kt | 4 +++ 5 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentConfigurationHolder.kt 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..e3fd29f3dd0 100644 --- a/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt +++ b/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt @@ -8,11 +8,16 @@ 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 -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -constructor( +class PaymentConfiguration constructor( val publishableKey: String, val stripeAccountId: String? = null ) : Parcelable { 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, From 0348ef999150a9a6d3697546c0b94c4d901748d0 Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Tue, 7 Jul 2026 17:11:20 -0400 Subject: [PATCH 2/2] update --- .../com/stripe/android/PaymentConfiguration.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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 e3fd29f3dd0..9eb00e227eb 100644 --- a/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt +++ b/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt @@ -17,11 +17,27 @@ import kotlinx.parcelize.Parcelize */ @Parcelize @Poko -class PaymentConfiguration constructor( +class PaymentConfiguration +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +constructor( val publishableKey: String, 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) }