diff --git a/financial-connections-lite/src/main/java/com/stripe/android/financialconnections/lite/repository/FinancialConnectionsLiteRepository.kt b/financial-connections-lite/src/main/java/com/stripe/android/financialconnections/lite/repository/FinancialConnectionsLiteRepository.kt index e9301ea16c0..9ec871d182b 100644 --- a/financial-connections-lite/src/main/java/com/stripe/android/financialconnections/lite/repository/FinancialConnectionsLiteRepository.kt +++ b/financial-connections-lite/src/main/java/com/stripe/android/financialconnections/lite/repository/FinancialConnectionsLiteRepository.kt @@ -24,8 +24,8 @@ internal class FinancialConnectionsLiteRepositoryImpl( ) : FinancialConnectionsLiteRepository { fun FinancialConnectionsSheetConfiguration.apiRequestOptions() = ApiRequest.Options( - publishableKeyProvider = { publishableKey }, - stripeAccountIdProvider = { stripeAccountId }, + apiKey = publishableKey, + stripeAccount = stripeAccountId, ) override suspend fun synchronize( diff --git a/payments-core/api/payments-core.api b/payments-core/api/payments-core.api index 3c4bf03b866..2d8703947b4 100644 --- a/payments-core/api/payments-core.api +++ b/payments-core/api/payments-core.api @@ -364,12 +364,14 @@ public final class com/stripe/android/PaymentConfiguration : android/os/Parcelab public static final field Companion Lcom/stripe/android/PaymentConfiguration$Companion; public final fun describeContents ()I public fun equals (Ljava/lang/Object;)Z + public final fun getBetas ()Ljava/util/Set; public static final fun getInstance (Landroid/content/Context;)Lcom/stripe/android/PaymentConfiguration; public final fun getPublishableKey ()Ljava/lang/String; public final fun getStripeAccountId ()Ljava/lang/String; public fun hashCode ()I public static final fun init (Landroid/content/Context;Ljava/lang/String;)V public static final fun init (Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V + public static final fun init (Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V public fun toString ()Ljava/lang/String; public final fun writeToParcel (Landroid/os/Parcel;I)V } @@ -378,7 +380,8 @@ public final class com/stripe/android/PaymentConfiguration$Companion { public final fun getInstance (Landroid/content/Context;)Lcom/stripe/android/PaymentConfiguration; public final fun init (Landroid/content/Context;Ljava/lang/String;)V public final fun init (Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V - public static synthetic fun init$default (Lcom/stripe/android/PaymentConfiguration$Companion;Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V + public final fun init (Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V + public static synthetic fun init$default (Lcom/stripe/android/PaymentConfiguration$Companion;Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;ILjava/lang/Object;)V } public final class com/stripe/android/PaymentIntentResult : com/stripe/android/StripeIntentResult { @@ -589,14 +592,6 @@ public final class com/stripe/android/Stripe$Companion { public final fun setAppInfo (Lcom/stripe/android/core/AppInfo;)V } -public final class com/stripe/android/StripeApiBeta : java/lang/Enum { - public static final field WeChatPayV1 Lcom/stripe/android/StripeApiBeta; - public final fun getCode ()Ljava/lang/String; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/stripe/android/StripeApiBeta; - public static fun values ()[Lcom/stripe/android/StripeApiBeta; -} - public abstract class com/stripe/android/StripeIntentResult : com/stripe/android/core/model/StripeModel { public static final field $stable I public fun equals (Ljava/lang/Object;)Z @@ -2138,6 +2133,7 @@ public final class com/stripe/android/model/PaymentMethod$Type : java/lang/Enum, public static final field Konbini Lcom/stripe/android/model/PaymentMethod$Type; public static final field Link Lcom/stripe/android/model/PaymentMethod$Type; public static final field MobilePay Lcom/stripe/android/model/PaymentMethod$Type; + public static final field Vipps Lcom/stripe/android/model/PaymentMethod$Type; public static final field Multibanco Lcom/stripe/android/model/PaymentMethod$Type; public static final field Netbanking Lcom/stripe/android/model/PaymentMethod$Type; public static final field Oxxo Lcom/stripe/android/model/PaymentMethod$Type; @@ -2333,6 +2329,10 @@ public final class com/stripe/android/model/PaymentMethodCreateParams : android/ public static final fun createMobilePay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createMobilePay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createMobilePay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createVipps ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createVipps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createVipps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createVipps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createMultibanco ()Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createMultibanco (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createMultibanco (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; @@ -2587,6 +2587,11 @@ public final class com/stripe/android/model/PaymentMethodCreateParams$Companion public final fun createMobilePay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createMobilePay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static synthetic fun createMobilePay$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createVipps ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createVipps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createVipps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createVipps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static synthetic fun createVipps$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createMultibanco ()Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createMultibanco (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createMultibanco (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; @@ -4323,4 +4328,3 @@ public final class com/stripe/android/view/i18n/TranslatorManager { public final fun getErrorMessageTranslator ()Lcom/stripe/android/view/i18n/ErrorMessageTranslator; public final fun setErrorMessageTranslator (Lcom/stripe/android/view/i18n/ErrorMessageTranslator;)V } - 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..b0ae17a518f 100644 --- a/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt +++ b/payments-core/src/main/java/com/stripe/android/PaymentConfiguration.kt @@ -14,7 +14,8 @@ class PaymentConfiguration @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) constructor( val publishableKey: String, - val stripeAccountId: String? = null + val stripeAccountId: String? = null, + val betas: Set = emptySet(), ) : Parcelable { init { @@ -36,11 +37,13 @@ constructor( @JvmSynthetic fun save( publishableKey: String, - stripeAccountId: String? + stripeAccountId: String?, + betas: Set, ) { prefs.edit() .putString(KEY_PUBLISHABLE_KEY, publishableKey) .putString(KEY_ACCOUNT_ID, stripeAccountId) + .putStringSet(KEY_BETAS, betas.mapTo(linkedSetOf()) { it.code }) .apply() } @@ -49,7 +52,10 @@ constructor( return prefs.getString(KEY_PUBLISHABLE_KEY, null)?.let { publishableKey -> PaymentConfiguration( publishableKey = publishableKey, - stripeAccountId = prefs.getString(KEY_ACCOUNT_ID, null) + stripeAccountId = prefs.getString(KEY_ACCOUNT_ID, null), + betas = prefs.getStringSet(KEY_BETAS, linkedSetOf()).orEmpty().mapNotNullTo(linkedSetOf()) { betaCode -> + StripeApiBeta.entries.firstOrNull { it.code == betaCode } + } ) } } @@ -59,6 +65,7 @@ constructor( private const val KEY_PUBLISHABLE_KEY = "key_publishable_key" private const val KEY_ACCOUNT_ID = "key_account_id" + private const val KEY_BETAS = "key_betas" } } @@ -95,16 +102,19 @@ constructor( fun init( context: Context, publishableKey: String, - stripeAccountId: String? = null + stripeAccountId: String? = null, + betas: Set = emptySet(), ) { instance = PaymentConfiguration( publishableKey = publishableKey, - stripeAccountId = stripeAccountId + stripeAccountId = stripeAccountId, + betas = betas, ) Store(context) .save( publishableKey = publishableKey, - stripeAccountId = stripeAccountId + stripeAccountId = stripeAccountId, + betas = betas, ) DefaultFraudDetectionDataRepository(context).refresh() diff --git a/payments-core/src/main/java/com/stripe/android/Stripe.kt b/payments-core/src/main/java/com/stripe/android/Stripe.kt index d54caa21ac5..48114dd79d9 100644 --- a/payments-core/src/main/java/com/stripe/android/Stripe.kt +++ b/payments-core/src/main/java/com/stripe/android/Stripe.kt @@ -112,7 +112,8 @@ class Stripe internal constructor( ), ApiKeyValidator.get().requireValid(publishableKey), stripeAccountId, - enableLogging + enableLogging, + betas, ) private constructor( @@ -120,14 +121,16 @@ class Stripe internal constructor( stripeRepository: StripeRepository, publishableKey: String, stripeAccountId: String?, - enableLogging: Boolean + enableLogging: Boolean, + betas: Set = emptySet(), ) : this( stripeRepository, StripePaymentController( context.applicationContext, { publishableKey }, stripeRepository, - enableLogging + enableLogging, + betasProvider = { betas }, ), publishableKey, stripeAccountId diff --git a/payments-core/src/main/java/com/stripe/android/StripePaymentController.kt b/payments-core/src/main/java/com/stripe/android/StripePaymentController.kt index beb65894915..243960c95af 100644 --- a/payments-core/src/main/java/com/stripe/android/StripePaymentController.kt +++ b/payments-core/src/main/java/com/stripe/android/StripePaymentController.kt @@ -56,13 +56,15 @@ constructor( private val paymentAnalyticsRequestFactory: PaymentAnalyticsRequestFactory = PaymentAnalyticsRequestFactory(context.applicationContext, publishableKeyProvider), private val alipayRepository: AlipayRepository = DefaultAlipayRepository(stripeRepository), - private val uiContext: CoroutineContext = Dispatchers.Main + private val uiContext: CoroutineContext = Dispatchers.Main, + private val betasProvider: () -> Set = { emptySet() }, ) : PaymentController { private val failureMessageFactory = PaymentFlowFailureMessageFactory(context) private val paymentIntentFlowResultProcessor = PaymentIntentFlowResultProcessor( context, publishableKeyProvider, + betasProvider(), stripeRepository, Logger.getInstance(enableLogging), workContext @@ -70,6 +72,7 @@ constructor( private val setupIntentFlowResultProcessor = SetupIntentFlowResultProcessor( context, publishableKeyProvider, + betasProvider(), stripeRepository, Logger.getInstance(enableLogging), workContext diff --git a/payments-core/src/main/java/com/stripe/android/googlepaylauncher/injection/GooglePayPaymentMethodLauncherModule.kt b/payments-core/src/main/java/com/stripe/android/googlepaylauncher/injection/GooglePayPaymentMethodLauncherModule.kt index 7fb059abd15..13439166289 100644 --- a/payments-core/src/main/java/com/stripe/android/googlepaylauncher/injection/GooglePayPaymentMethodLauncherModule.kt +++ b/payments-core/src/main/java/com/stripe/android/googlepaylauncher/injection/GooglePayPaymentMethodLauncherModule.kt @@ -1,5 +1,7 @@ package com.stripe.android.googlepaylauncher.injection +import com.stripe.android.StripeApiBeta +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.networking.AnalyticsRequestFactory import com.stripe.android.googlepaylauncher.DefaultGooglePayRepository import com.stripe.android.googlepaylauncher.DefaultPaymentsClientFactory @@ -12,7 +14,9 @@ import com.stripe.android.payments.core.analytics.RealErrorReporter import dagger.Binds import dagger.Module import dagger.Provides +import javax.inject.Named import javax.inject.Singleton +import kotlin.jvm.JvmSuppressWildcards @Module( subcomponents = [GooglePayPaymentMethodLauncherViewModelSubcomponent::class] @@ -50,5 +54,9 @@ internal abstract class GooglePayPaymentMethodLauncherModule { googlePayConfig: GooglePayPaymentMethodLauncher.Config, paymentsClientFactory: PaymentsClientFactory ) = paymentsClientFactory.create(googlePayConfig.environment) + + @Provides + @Named(STRIPE_API_BETAS) + fun provideStripeApiBetas(): Set<@JvmSuppressWildcards StripeApiBeta> = emptySet() } } diff --git a/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt b/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt index d6baa28a951..0761c48f717 100644 --- a/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt +++ b/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt @@ -409,6 +409,14 @@ constructor( requiresMandateForPaymentIntent = false, hasDelayedSettlement = false, ), + Vipps( + "vipps", + isReusable = false, + isVoucher = false, + requiresMandate = false, + requiresMandateForPaymentIntent = false, + hasDelayedSettlement = false, + ), Multibanco( "multibanco", isReusable = false, diff --git a/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt b/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt index 37595e70ea8..ce6ea1a963f 100644 --- a/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt +++ b/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt @@ -1349,6 +1349,21 @@ constructor( ) } + @JvmStatic + @JvmOverloads + fun createVipps( + billingDetails: PaymentMethod.BillingDetails? = null, + metadata: Map? = null, + allowRedisplay: PaymentMethod.AllowRedisplay? = null, + ): PaymentMethodCreateParams { + return PaymentMethodCreateParams( + type = PaymentMethod.Type.Vipps, + billingDetails = billingDetails, + metadata = metadata, + allowRedisplay = allowRedisplay, + ) + } + @JvmStatic @JvmOverloads fun createTwint( diff --git a/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt b/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt index ee2589a7a1c..6f31f7b851b 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt @@ -3,6 +3,7 @@ package com.stripe.android.payments import android.content.Context import com.stripe.android.PaymentController import com.stripe.android.PaymentIntentResult +import com.stripe.android.StripeApiBeta import com.stripe.android.SetupIntentResult import com.stripe.android.StripeIntentResult import com.stripe.android.StripeIntentResult.Outcome.Companion.CANCELED @@ -10,6 +11,7 @@ import com.stripe.android.StripeIntentResult.Outcome.Companion.SUCCEEDED import com.stripe.android.core.Logger import com.stripe.android.core.injection.IOContext import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.networking.ApiRequest import com.stripe.android.model.PaymentIntent import com.stripe.android.model.PaymentMethod @@ -22,16 +24,17 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import javax.inject.Inject import javax.inject.Named -import javax.inject.Provider import javax.inject.Singleton import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmSuppressWildcards /** * Class responsible for processing the result of a [PaymentController] confirm operation. */ internal sealed class PaymentFlowResultProcessor>( context: Context, - private val publishableKeyProvider: Provider, + private val publishableKeyProvider: () -> String, + private val betasProvider: () -> Set<@JvmSuppressWildcards StripeApiBeta>, protected val stripeRepository: StripeRepository, private val logger: Logger, private val workContext: CoroutineContext, @@ -46,8 +49,9 @@ internal sealed class PaymentFlowResultProcessor String, + @Named(STRIPE_API_BETAS) betas: Set<@JvmSuppressWildcards StripeApiBeta>, stripeRepository: StripeRepository, logger: Logger, @IOContext workContext: CoroutineContext ) : PaymentFlowResultProcessor( context, publishableKeyProvider, + { betas }, stripeRepository, logger, workContext @@ -374,12 +380,14 @@ internal class PaymentIntentFlowResultProcessor @Inject constructor( internal class SetupIntentFlowResultProcessor @Inject constructor( context: Context, @Named(PUBLISHABLE_KEY) publishableKeyProvider: () -> String, + @Named(STRIPE_API_BETAS) betas: Set<@JvmSuppressWildcards StripeApiBeta>, stripeRepository: StripeRepository, logger: Logger, @IOContext workContext: CoroutineContext ) : PaymentFlowResultProcessor( context, publishableKeyProvider, + { betas }, stripeRepository, logger, workContext 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..da0cd7d6431 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 @@ -3,12 +3,15 @@ package com.stripe.android.payments.core.injection import android.content.Context import androidx.annotation.RestrictTo import com.stripe.android.PaymentConfiguration +import com.stripe.android.StripeApiBeta import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID import dagger.Module import dagger.Provides import javax.inject.Named import javax.inject.Provider +import kotlin.jvm.JvmSuppressWildcards @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Module @@ -29,4 +32,10 @@ class PaymentConfigurationModule { fun provideStripeAccountId( paymentConfiguration: Provider ): () -> String? = { paymentConfiguration.get().stripeAccountId } + + @Provides + @Named(STRIPE_API_BETAS) + fun provideStripeApiBetas( + paymentConfiguration: Provider + ): Set<@JvmSuppressWildcards StripeApiBeta> = paymentConfiguration.get().betas } diff --git a/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentLauncherModule.kt b/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentLauncherModule.kt index 7bb2568929f..d42e62488e8 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentLauncherModule.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/core/injection/PaymentLauncherModule.kt @@ -2,9 +2,11 @@ package com.stripe.android.payments.core.injection import android.content.Context import com.google.android.instantapps.InstantApps +import com.stripe.android.StripeApiBeta import com.stripe.android.core.injection.ENABLE_LOGGING import com.stripe.android.core.injection.IOContext import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.injection.UIContext import com.stripe.android.core.utils.DefaultDurationProvider import com.stripe.android.core.utils.DurationProvider @@ -17,6 +19,7 @@ import dagger.Provides import javax.inject.Named import javax.inject.Singleton import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmSuppressWildcards @Module( subcomponents = [PaymentLauncherViewModelSubcomponent::class] @@ -60,6 +63,10 @@ internal class PaymentLauncherModule { return InstantApps.isInstantApp(context) } + @Provides + @Named(STRIPE_API_BETAS) + fun provideStripeApiBetas(): Set<@JvmSuppressWildcards StripeApiBeta> = emptySet() + @Provides @Singleton fun provideDurationProvider(): DurationProvider { diff --git a/payments-core/src/main/java/com/stripe/android/polling/DefaultIntentStatusPoller.kt b/payments-core/src/main/java/com/stripe/android/polling/DefaultIntentStatusPoller.kt index f23fe044493..a748e233120 100644 --- a/payments-core/src/main/java/com/stripe/android/polling/DefaultIntentStatusPoller.kt +++ b/payments-core/src/main/java/com/stripe/android/polling/DefaultIntentStatusPoller.kt @@ -60,8 +60,9 @@ class DefaultIntentStatusPoller @Inject constructor( val paymentIntent = stripeRepository.retrievePaymentIntent( clientSecret = config.clientSecret, options = ApiRequest.Options( - publishableKeyProvider = { paymentConfig.publishableKey }, - stripeAccountIdProvider = { paymentConfig.stripeAccountId }, + apiKey = paymentConfig.publishableKey, + stripeAccount = paymentConfig.stripeAccountId, + betas = paymentConfig.betas, ), ) return paymentIntent.getOrNull()?.status diff --git a/payments-core/src/test/java/com/stripe/android/PaymentConfigurationTest.kt b/payments-core/src/test/java/com/stripe/android/PaymentConfigurationTest.kt index 48dc2ad8162..a5398f421f1 100644 --- a/payments-core/src/test/java/com/stripe/android/PaymentConfigurationTest.kt +++ b/payments-core/src/test/java/com/stripe/android/PaymentConfigurationTest.kt @@ -45,6 +45,24 @@ class PaymentConfigurationTest { ) } + @Test + fun `betas should be persisted`() { + PaymentConfiguration.init( + context, + ApiKeyFixtures.FAKE_PUBLISHABLE_KEY, + betas = setOf(StripeApiBeta.VippsPreviewV1) + ) + + assertThat( + PaymentConfiguration.getInstance(context) + ).isEqualTo( + PaymentConfiguration( + ApiKeyFixtures.FAKE_PUBLISHABLE_KEY, + betas = setOf(StripeApiBeta.VippsPreviewV1) + ) + ) + } + @Test fun getInstance_whenInstanceIsNull_loadsFromPrefs() { PaymentConfiguration.init( diff --git a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt index 430f25a0d20..49084df5a9a 100644 --- a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt +++ b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt @@ -112,6 +112,10 @@ internal object PaymentMethodCreateParamsFixtures { billingDetails = BILLING_DETAILS, ) + internal val VIPPS = PaymentMethodCreateParams.createVipps( + billingDetails = BILLING_DETAILS, + ) + internal val TWINT = PaymentMethodCreateParams.createTwint( billingDetails = BILLING_DETAILS ) diff --git a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt index 35f0ad6c487..d2cd44da2ab 100644 --- a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt +++ b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt @@ -111,6 +111,28 @@ class PaymentMethodCreateParamsTest { ) } + @Test + fun createVipps() { + assertThat(PaymentMethodCreateParamsFixtures.VIPPS.toParamMap()) + .isEqualTo( + mapOf( + "type" to "vipps", + "billing_details" to mapOf( + "address" to mapOf( + "city" to "San Francisco", + "country" to "US", + "line1" to "1234 Main St", + "state" to "CA", + "postal_code" to "94111" + ), + "email" to "jenny.rosen@example.com", + "name" to "Jenny Rosen", + "phone" to "1-800-555-1234" + ) + ) + ) + } + @Test fun auBecsDebit_toParamMap_shouldCreateExpectedMap() { assertThat(PaymentMethodCreateParamsFixtures.AU_BECS_DEBIT.toParamMap()) diff --git a/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt b/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt index aa147c0df2b..2cdb5ca35e3 100644 --- a/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt +++ b/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt @@ -4,6 +4,7 @@ import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import com.stripe.android.ApiKeyFixtures import com.stripe.android.PaymentIntentResult +import com.stripe.android.StripeApiBeta import com.stripe.android.StripeIntentResult import com.stripe.android.core.Logger import com.stripe.android.core.exception.APIConnectionException @@ -144,6 +145,32 @@ internal class PaymentIntentFlowResultProcessorTest { ) } + @Test + fun `processResult forwards configured betas when retrieving the intent`() = runTest(testDispatcher) { + whenever(mockStripeRepository.retrievePaymentIntent(any(), any(), any())).thenReturn( + Result.success(PaymentIntentFixtures.PI_SUCCEEDED) + ) + + val clientSecret = requireNotNull(PaymentIntentFixtures.PI_SUCCEEDED.clientSecret) + val requestOptions = ApiRequest.Options( + apiKey = ApiKeyFixtures.FAKE_PUBLISHABLE_KEY, + betas = setOf(StripeApiBeta.VippsPreviewV1), + ) + + createProcessor(betas = setOf(StripeApiBeta.VippsPreviewV1)).processResult( + PaymentFlowResult.Unvalidated( + clientSecret = clientSecret, + flowOutcome = StripeIntentResult.Outcome.SUCCEEDED, + ) + ) + + verify(mockStripeRepository).retrievePaymentIntent( + eq(clientSecret), + eq(requestOptions), + eq(PaymentFlowResultProcessor.EXPAND_PAYMENT_METHOD), + ) + } + @Test fun `no refresh when user cancels the payment`() = runTest(testDispatcher) { whenever(mockStripeRepository.retrievePaymentIntent(any(), any(), any())).thenReturn( @@ -1008,9 +1035,11 @@ internal class PaymentIntentFlowResultProcessorTest { private fun createProcessor( stripeRepository: StripeRepository = mockStripeRepository, + betas: Set = emptySet(), ): PaymentIntentFlowResultProcessor = PaymentIntentFlowResultProcessor( ApplicationProvider.getApplicationContext(), { ApiKeyFixtures.FAKE_PUBLISHABLE_KEY }, + betas, stripeRepository, Logger.noop(), testDispatcher diff --git a/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt b/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt index 37a35f00252..34afc99b041 100644 --- a/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt +++ b/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt @@ -4,6 +4,7 @@ import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import com.stripe.android.ApiKeyFixtures import com.stripe.android.SetupIntentResult +import com.stripe.android.StripeApiBeta import com.stripe.android.StripeIntentResult import com.stripe.android.core.Logger import com.stripe.android.core.networking.ApiRequest @@ -36,6 +37,7 @@ internal class SetupIntentFlowResultProcessorTest { private val processor = SetupIntentFlowResultProcessor( ApplicationProvider.getApplicationContext(), { ApiKeyFixtures.FAKE_PUBLISHABLE_KEY }, + emptySet(), mockStripeRepository, Logger.noop(), testDispatcher diff --git a/payments-core/src/test/java/com/stripe/android/polling/DefaultIntentStatusPollerTest.kt b/payments-core/src/test/java/com/stripe/android/polling/DefaultIntentStatusPollerTest.kt index 0a7d1ae897f..06c36f73b2d 100644 --- a/payments-core/src/test/java/com/stripe/android/polling/DefaultIntentStatusPollerTest.kt +++ b/payments-core/src/test/java/com/stripe/android/polling/DefaultIntentStatusPollerTest.kt @@ -1,6 +1,8 @@ package com.stripe.android.polling import com.google.common.truth.Truth.assertThat +import com.stripe.android.PaymentConfiguration +import com.stripe.android.StripeApiBeta import com.stripe.android.model.StripeIntent.Status.RequiresAction import com.stripe.android.model.StripeIntent.Status.RequiresCapture import com.stripe.android.model.StripeIntent.Status.Succeeded @@ -148,4 +150,23 @@ class DefaultIntentStatusPollerTest { poller.stopPolling() } + + @Test + fun `Force poll uses payment configuration betas`() = runTest(testDispatcher) { + var requestOptions: com.stripe.android.core.networking.ApiRequest.Options? = null + val poller = createIntentStatusPoller( + enqueuedStatuses = listOf(RequiresAction), + dispatcher = testDispatcher, + paymentConfiguration = PaymentConfiguration( + publishableKey = "key", + stripeAccountId = "account_id", + betas = setOf(StripeApiBeta.VippsPreviewV1), + ), + onRetrievePaymentIntent = { requestOptions = it }, + ) + + poller.forcePoll() + + assertThat(requestOptions?.betas).containsExactly(StripeApiBeta.VippsPreviewV1) + } } diff --git a/payments-core/src/test/java/com/stripe/android/polling/PollingTestUtils.kt b/payments-core/src/test/java/com/stripe/android/polling/PollingTestUtils.kt index aa211d79065..ee7ae7751e6 100644 --- a/payments-core/src/test/java/com/stripe/android/polling/PollingTestUtils.kt +++ b/payments-core/src/test/java/com/stripe/android/polling/PollingTestUtils.kt @@ -11,15 +11,18 @@ import kotlinx.coroutines.CoroutineDispatcher internal fun createIntentStatusPoller( enqueuedStatuses: List, dispatcher: CoroutineDispatcher, + paymentConfiguration: PaymentConfiguration = PaymentConfiguration( + publishableKey = "key", + stripeAccountId = "account_id", + ), + onRetrievePaymentIntent: (ApiRequest.Options) -> Unit = {}, ): DefaultIntentStatusPoller { return DefaultIntentStatusPoller( - stripeRepository = FakeStripeRepository(enqueuedStatuses), - paymentConfigProvider = { - PaymentConfiguration( - publishableKey = "key", - stripeAccountId = "account_id", - ) - }, + stripeRepository = FakeStripeRepository( + enqueuedStatuses = enqueuedStatuses, + onRetrievePaymentIntent = onRetrievePaymentIntent, + ), + paymentConfigProvider = { paymentConfiguration }, config = IntentStatusPoller.Config( clientSecret = "secret", ), @@ -28,7 +31,8 @@ internal fun createIntentStatusPoller( } private class FakeStripeRepository( - enqueuedStatuses: List + enqueuedStatuses: List, + private val onRetrievePaymentIntent: (ApiRequest.Options) -> Unit, ) : AbsFakeStripeRepository() { private val queue = enqueuedStatuses.toMutableList() @@ -38,6 +42,7 @@ private class FakeStripeRepository( options: ApiRequest.Options, expandFields: List ): Result { + onRetrievePaymentIntent(options) val intentStatus = queue.removeAt(0) val paymentIntent = PaymentIntentFactory.create(status = intentStatus) return Result.success(paymentIntent) diff --git a/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_vipps_day.xml b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_vipps_day.xml new file mode 100644 index 00000000000..e3c9fd2d9f2 --- /dev/null +++ b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_vipps_day.xml @@ -0,0 +1,13 @@ + + + + diff --git a/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_vipps_night.xml b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_vipps_night.xml new file mode 100644 index 00000000000..e3c9fd2d9f2 --- /dev/null +++ b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_vipps_night.xml @@ -0,0 +1,13 @@ + + + + diff --git a/payments-ui-core/res/values/donottranslate.xml b/payments-ui-core/res/values/donottranslate.xml index dc8b1c2583f..b5c4a4288d8 100644 --- a/payments-ui-core/res/values/donottranslate.xml +++ b/payments-ui-core/res/values/donottranslate.xml @@ -28,6 +28,7 @@ Crypto Alma MobilePay + Vipps Zip BLIK Alipay diff --git a/payments-ui-core/src/main/resources/lpms.json b/payments-ui-core/src/main/resources/lpms.json index c904842e3b6..b32e266bf3f 100644 --- a/payments-ui-core/src/main/resources/lpms.json +++ b/payments-ui-core/src/main/resources/lpms.json @@ -842,6 +842,27 @@ } } }, + { + "type": "vipps", + "async": false, + "fields": [], + "next_action_spec": { + "confirm_response_status_specs": { + "requires_action": { + "type": "redirect_to_url", + "native_mobile_redirect_strategy": "follow_redirects" + } + }, + "post_confirm_handling_pi_status_specs": { + "succeeded": { + "type": "finished" + }, + "requires_action": { + "type": "canceled" + } + } + } + }, { "type": "zip", "async": false, diff --git a/paymentsheet/src/main/java/com/stripe/android/checkout/injection/CheckoutComponent.kt b/paymentsheet/src/main/java/com/stripe/android/checkout/injection/CheckoutComponent.kt index d82f9ffdab2..93f5f107628 100644 --- a/paymentsheet/src/main/java/com/stripe/android/checkout/injection/CheckoutComponent.kt +++ b/paymentsheet/src/main/java/com/stripe/android/checkout/injection/CheckoutComponent.kt @@ -71,6 +71,7 @@ internal object CheckoutModule { ): ApiRequest.Options = ApiRequest.Options( apiKey = paymentConfiguration.get().publishableKey, stripeAccount = paymentConfiguration.get().stripeAccountId, + betas = paymentConfiguration.get().betas, ) @Provides diff --git a/paymentsheet/src/main/java/com/stripe/android/common/taptoadd/TapToAddCollectionHandler.kt b/paymentsheet/src/main/java/com/stripe/android/common/taptoadd/TapToAddCollectionHandler.kt index 8dcf903558d..83ae6e7e07e 100644 --- a/paymentsheet/src/main/java/com/stripe/android/common/taptoadd/TapToAddCollectionHandler.kt +++ b/paymentsheet/src/main/java/com/stripe/android/common/taptoadd/TapToAddCollectionHandler.kt @@ -357,6 +357,7 @@ internal class DefaultTapToAddCollectionHandler( return ApiRequest.Options( apiKey = ephemeralKeySecret, stripeAccount = paymentConfiguration.stripeAccountId, + betas = paymentConfiguration.betas, ) } diff --git a/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt b/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt index ff3ab6a8bc4..3d23f8148e2 100644 --- a/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt +++ b/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt @@ -6,11 +6,13 @@ import androidx.lifecycle.SavedStateHandle import com.stripe.android.BuildConfig import com.stripe.android.PaymentConfiguration import com.stripe.android.Stripe +import com.stripe.android.StripeApiBeta import com.stripe.android.cards.CardAccountRangeRepository import com.stripe.android.cards.DefaultCardAccountRangeRepositoryFactory import com.stripe.android.core.Logger import com.stripe.android.core.injection.ENABLE_LOGGING import com.stripe.android.core.injection.IOContext +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.networking.AnalyticsRequestFactory import com.stripe.android.core.networking.DefaultStripeNetworkClient import com.stripe.android.core.utils.DefaultDurationProvider @@ -211,6 +213,13 @@ internal interface NativeLinkModule { return PaymentConfiguration.getInstance(appContext) } + @Provides + @NativeLinkScope + @Named(STRIPE_API_BETAS) + fun provideStripeApiBetas( + paymentConfiguration: PaymentConfiguration, + ): Set<@JvmSuppressWildcards StripeApiBeta> = paymentConfiguration.betas + @Provides @NativeLinkScope @Named(ALLOWS_MANUAL_CONFIRMATION) diff --git a/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt index 0ba725d1639..56fb9adee25 100644 --- a/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt +++ b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt @@ -37,6 +37,7 @@ import com.stripe.android.lpmfoundations.paymentmethod.definitions.SunbitDefinit import com.stripe.android.lpmfoundations.paymentmethod.definitions.SwishDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.TwintDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.UsBankAccountDefinition +import com.stripe.android.lpmfoundations.paymentmethod.definitions.VippsDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.WeChatPayDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.WeroDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.ZipDefinition @@ -66,6 +67,7 @@ internal object PaymentMethodRegistry { KlarnaDefinition, KonbiniDefinition, MobilePayDefinition, + VippsDefinition, MultibancoDefinition, OxxoDefinition, P24Definition, diff --git a/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/VippsDefinition.kt b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/VippsDefinition.kt new file mode 100644 index 00000000000..29d91700a9e --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/VippsDefinition.kt @@ -0,0 +1,36 @@ +package com.stripe.android.lpmfoundations.paymentmethod.definitions + +import com.stripe.android.lpmfoundations.luxe.SupportedPaymentMethod +import com.stripe.android.lpmfoundations.paymentmethod.AddPaymentMethodRequirement +import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodDefinition +import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata +import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory +import com.stripe.android.model.PaymentMethod +import com.stripe.android.ui.core.R + +internal object VippsDefinition : PaymentMethodDefinition { + override val type: PaymentMethod.Type = PaymentMethod.Type.Vipps + + override val supportedAsSavedPaymentMethod: Boolean = false + + override fun requirementsToBeUsedAsNewPaymentMethod( + hasIntentToSetup: Boolean + ): Set = setOf( + AddPaymentMethodRequirement.UnsupportedForSetup, + ) + + override fun requiresMandate(metadata: PaymentMethodMetadata): Boolean = false + + override fun uiDefinitionFactory( + metadata: PaymentMethodMetadata + ): UiDefinitionFactory = VippsUiDefinitionFactory +} + +private object VippsUiDefinitionFactory : UiDefinitionFactory.Simple() { + override fun createSupportedPaymentMethod(metadata: PaymentMethodMetadata) = SupportedPaymentMethod( + paymentMethodDefinition = VippsDefinition, + displayNameResource = R.string.stripe_paymentsheet_payment_method_vipps, + iconResource = R.drawable.stripe_ic_paymentsheet_pm_vipps_day, + iconResourceNight = R.drawable.stripe_ic_paymentsheet_pm_vipps_night, + ) +} diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepository.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepository.kt index 8ff5c873f25..1d470180050 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepository.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepository.kt @@ -1,8 +1,10 @@ package com.stripe.android.paymentsheet.repositories +import com.stripe.android.StripeApiBeta import com.stripe.android.Stripe import com.stripe.android.checkout.Address import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID import com.stripe.android.core.model.parsers.StripeErrorJsonParser import com.stripe.android.core.networking.ApiRequest @@ -15,6 +17,7 @@ import java.util.TimeZone import java.util.UUID import javax.inject.Inject import javax.inject.Named +import kotlin.jvm.JvmSuppressWildcards @OptIn(CheckoutSessionPreview::class) internal class CheckoutSessionRepository @Inject constructor( @@ -22,6 +25,7 @@ internal class CheckoutSessionRepository @Inject constructor( private val stripeNetworkClient: StripeNetworkClient, @Named(PUBLISHABLE_KEY) private val publishableKeyProvider: () -> String, @Named(STRIPE_ACCOUNT_ID) private val stripeAccountIdProvider: () -> String?, + @Named(STRIPE_API_BETAS) private val betas: Set<@JvmSuppressWildcards StripeApiBeta>, ) { private val apiRequestFactory = ApiRequest.Factory( @@ -34,6 +38,7 @@ internal class CheckoutSessionRepository @Inject constructor( private fun createOptions(): ApiRequest.Options = ApiRequest.Options( apiKey = publishableKeyProvider(), stripeAccount = stripeAccountIdProvider(), + betas = betas, ) private suspend fun executePost( diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt index 820a3f6e4bb..b99d8115114 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt @@ -35,6 +35,14 @@ internal class CustomerApiRepository @Inject constructor( @IOContext private val workContext: CoroutineContext, @Named(PRODUCT_USAGE) private val productUsageTokens: Set = emptySet() ) : CustomerRepository { + private fun createRequestOptions(apiKey: String): ApiRequest.Options { + val paymentConfiguration = lazyPaymentConfig.get() + return ApiRequest.Options( + apiKey = apiKey, + stripeAccount = paymentConfiguration.stripeAccountId, + betas = paymentConfiguration.betas, + ) + } override suspend fun retrieveCustomer( customerId: String, @@ -43,10 +51,7 @@ internal class CustomerApiRepository @Inject constructor( return stripeRepository.retrieveCustomer( customerId, productUsageTokens, - ApiRequest.Options( - ephemeralKeySecret, - lazyPaymentConfig.get().stripeAccountId - ) + createRequestOptions(ephemeralKeySecret) ).getOrNull() } @@ -71,10 +76,7 @@ internal class CustomerApiRepository @Inject constructor( paymentMethodType = paymentMethodType, ), productUsageTokens = productUsageTokens, - requestOptions = ApiRequest.Options( - apiKey = ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, - ), + requestOptions = createRequestOptions(ephemeralKeySecret), ).onFailure { logger.error("Failed to retrieve payment methods.", it) errorReporter.report( @@ -112,10 +114,7 @@ internal class CustomerApiRepository @Inject constructor( return stripeRepository.detachPaymentMethod( productUsageTokens = productUsageTokens, paymentMethodId = paymentMethodId, - requestOptions = ApiRequest.Options( - apiKey = ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, - ), + requestOptions = createRequestOptions(ephemeralKeySecret), ).onFailure { logger.error("Failed to detach payment method $paymentMethodId.", it) } @@ -134,10 +133,7 @@ internal class CustomerApiRepository @Inject constructor( customerSessionClientSecret: String, paymentMethodId: String, ): Result = with(CoroutineScope(workContext)) { - val requestOptions = ApiRequest.Options( - apiKey = ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, - ) + val requestOptions = createRequestOptions(ephemeralKeySecret) val detachOne: suspend (String) -> Result = { pmId -> stripeRepository.detachPaymentMethod( @@ -217,10 +213,7 @@ internal class CustomerApiRepository @Inject constructor( customerId = customerId, productUsageTokens = productUsageTokens, paymentMethodId = paymentMethodId, - requestOptions = ApiRequest.Options( - apiKey = ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, - ) + requestOptions = createRequestOptions(ephemeralKeySecret) ).onFailure { logger.error("Failed to attach payment method $paymentMethodId.", it) } @@ -234,10 +227,7 @@ internal class CustomerApiRepository @Inject constructor( stripeRepository.updatePaymentMethod( paymentMethodId = paymentMethodId, paymentMethodUpdateParams = params, - options = ApiRequest.Options( - apiKey = ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, - ) + options = createRequestOptions(ephemeralKeySecret) ).onFailure { logger.error("Failed to update payment method $paymentMethodId.", it) } @@ -249,10 +239,7 @@ internal class CustomerApiRepository @Inject constructor( ): Result = stripeRepository.setDefaultPaymentMethod( paymentMethodId = paymentMethodId, customerId = customerId, - options = ApiRequest.Options( - apiKey = ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, - ) + options = createRequestOptions(ephemeralKeySecret) ) override suspend fun retrievePaymentMethod( @@ -264,10 +251,7 @@ internal class CustomerApiRepository @Inject constructor( customerId = customerId, paymentMethodId = paymentMethodId, productUsageTokens = productUsageTokens, - requestOptions = ApiRequest.Options( - apiKey = ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, - ), + requestOptions = createRequestOptions(ephemeralKeySecret), ).onFailure { logger.error("Failed to retrieve payment method $paymentMethodId.", it) } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/ElementsSessionRepository.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/ElementsSessionRepository.kt index 4a9c2af97a7..8880fabd2d5 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/ElementsSessionRepository.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/ElementsSessionRepository.kt @@ -68,6 +68,7 @@ internal class RealElementsSessionRepository @Inject constructor( get() = ApiRequest.Options( apiKey = lazyPaymentConfig.get().publishableKey, stripeAccount = lazyPaymentConfig.get().stripeAccountId, + betas = lazyPaymentConfig.get().betas, ) override suspend fun get( diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentMethodMessagingPromotionsHelper.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentMethodMessagingPromotionsHelper.kt index b180c9c0d6f..fb8a36a65d6 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentMethodMessagingPromotionsHelper.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentMethodMessagingPromotionsHelper.kt @@ -69,7 +69,8 @@ internal class DefaultPaymentMethodMessagePromotionsHelper @Inject constructor( locale = Locale.getDefault().language, requestOptions = ApiRequest.Options( apiKey = lazyPaymentConfig.get().publishableKey, - stripeAccount = lazyPaymentConfig.get().stripeAccountId + stripeAccount = lazyPaymentConfig.get().stripeAccountId, + betas = lazyPaymentConfig.get().betas, ) ) } diff --git a/paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/VippsDefinitionTest.kt b/paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/VippsDefinitionTest.kt new file mode 100644 index 00000000000..0bb1f3259ab --- /dev/null +++ b/paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/VippsDefinitionTest.kt @@ -0,0 +1,23 @@ +package com.stripe.android.lpmfoundations.paymentmethod.definitions + +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class VippsDefinitionTest { + @Test + fun `createFormElements returns no elements`() { + VippsDefinition.basicEmptyFormTest() + } + + @Test + fun `createFormElements returns requested contact information fields`() { + VippsDefinition.basicFormWithContactFieldsTest() + } + + @Test + fun `createFormElements returns all billing details fields`() { + VippsDefinition.basicFormWithBillingInformationTest() + } +} diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt index 1f3e583042e..9cc4562166e 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt @@ -160,6 +160,7 @@ internal suspend fun createIntentConfirmationInterceptor( stripeNetworkClient = DefaultStripeNetworkClient(), publishableKeyProvider = { "pk" }, stripeAccountIdProvider = { null }, + betas = emptySet(), ), requestOptions = requestOptions, ) diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt index 64c36496da6..5ea97dc5264 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt @@ -14,11 +14,13 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.CreationExtras import androidx.test.core.app.ActivityScenario import com.stripe.android.PaymentConfiguration +import com.stripe.android.StripeApiBeta import com.stripe.android.common.di.ElementsSessionClientParamsModule import com.stripe.android.core.Logger import com.stripe.android.core.injection.ENABLE_LOGGING import com.stripe.android.core.injection.IOContext import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID import com.stripe.android.core.injection.UIContext import com.stripe.android.core.networking.AnalyticsRequestFactory @@ -69,6 +71,7 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmSuppressWildcards internal fun extendedPaymentElementConfirmationTest( application: Application, @@ -193,6 +196,10 @@ internal interface ExtendedPaymentElementConfirmationTestModule { @Provides fun providesErrorReporter(): ErrorReporter = FakeErrorReporter() + @Provides + @Named(STRIPE_API_BETAS) + fun providesStripeApiBetas(): Set<@JvmSuppressWildcards StripeApiBeta> = emptySet() + @Provides fun provideDurationProvider(): DurationProvider = FakeDurationProvider() diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt index 8d2909622f5..b83ec94b266 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt @@ -14,11 +14,13 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.CreationExtras import androidx.test.core.app.ActivityScenario import com.stripe.android.PaymentConfiguration +import com.stripe.android.StripeApiBeta import com.stripe.android.common.di.ElementsSessionClientParamsModule import com.stripe.android.core.Logger import com.stripe.android.core.injection.ENABLE_LOGGING import com.stripe.android.core.injection.IOContext import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID import com.stripe.android.core.injection.UIContext import com.stripe.android.core.networking.AnalyticsRequestFactory @@ -65,6 +67,7 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmSuppressWildcards internal fun paymentElementConfirmationTest( application: Application, @@ -188,6 +191,10 @@ internal interface PaymentElementConfirmationTestModule { @Provides fun providesErrorReporter(): ErrorReporter = FakeErrorReporter() + @Provides + @Named(STRIPE_API_BETAS) + fun providesStripeApiBetas(): Set<@JvmSuppressWildcards StripeApiBeta> = emptySet() + @Provides fun provideDurationProvider(): DurationProvider = FakeDurationProvider() diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/intent/CheckoutSessionConfirmationInterceptorTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/intent/CheckoutSessionConfirmationInterceptorTest.kt index 0ad861f26cc..10622ad68ef 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/intent/CheckoutSessionConfirmationInterceptorTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/intent/CheckoutSessionConfirmationInterceptorTest.kt @@ -515,6 +515,7 @@ class CheckoutSessionConfirmationInterceptorTest { stripeNetworkClient = DefaultStripeNetworkClient(), publishableKeyProvider = { "pk_test_123" }, stripeAccountIdProvider = { null }, + betas = emptySet(), ) val interceptor = CheckoutSessionConfirmationInterceptor( diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/LpmNetworkTestActivity.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/LpmNetworkTestActivity.kt index 5bf85b39433..fe20cf0ae96 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/LpmNetworkTestActivity.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/lpms/foundations/LpmNetworkTestActivity.kt @@ -13,11 +13,13 @@ import androidx.lifecycle.createSavedStateHandle import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.CreationExtras import com.stripe.android.PaymentConfiguration +import com.stripe.android.StripeApiBeta import com.stripe.android.common.di.ElementsSessionClientParamsModule import com.stripe.android.core.Logger import com.stripe.android.core.injection.ENABLE_LOGGING import com.stripe.android.core.injection.IOContext import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID import com.stripe.android.core.networking.AnalyticsRequestFactory import com.stripe.android.core.utils.DurationProvider @@ -53,6 +55,7 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmSuppressWildcards internal class LpmNetworkTestActivity : AppCompatActivity() { val viewModel: TestViewModel by viewModels { @@ -210,6 +213,10 @@ internal interface LpmNetworkTestModule { @Named(ENABLE_LOGGING) fun providesEnableLogging(): Boolean = ENABLE_LOGGING_VALUE + @Provides + @Named(STRIPE_API_BETAS) + fun providesStripeApiBetas(): Set<@JvmSuppressWildcards StripeApiBeta> = emptySet() + @Provides @Singleton @IOContext diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepositoryTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepositoryTest.kt index 6a067b39ce6..1a90d7350d4 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepositoryTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CheckoutSessionRepositoryTest.kt @@ -1,6 +1,7 @@ package com.stripe.android.paymentsheet.repositories import com.google.common.truth.Truth.assertThat +import com.stripe.android.StripeApiBeta import com.stripe.android.checkouttesting.DEFAULT_CHECKOUT_SESSION_ID import com.stripe.android.checkouttesting.checkoutInit import com.stripe.android.checkouttesting.checkoutUpdate @@ -8,6 +9,7 @@ import com.stripe.android.core.networking.AnalyticsRequestFactory import com.stripe.android.core.networking.DefaultStripeNetworkClient import com.stripe.android.networktesting.NetworkRule import com.stripe.android.networktesting.RequestMatchers.bodyPart +import com.stripe.android.networktesting.RequestMatchers.header import com.stripe.android.networktesting.testBodyFromFile import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -31,6 +33,7 @@ class CheckoutSessionRepositoryTest { stripeNetworkClient = DefaultStripeNetworkClient(), publishableKeyProvider = { "pk_test_123" }, stripeAccountIdProvider = { null }, + betas = emptySet(), ) @Test @@ -52,6 +55,30 @@ class CheckoutSessionRepositoryTest { assertThat(result.isSuccess).isTrue() } + @Test + fun `init forwards preview betas from merchant-owned request options`() = runTest { + val repository = CheckoutSessionRepository( + clientParams = clientParams, + stripeNetworkClient = DefaultStripeNetworkClient(), + publishableKeyProvider = { "pk_test_123" }, + stripeAccountIdProvider = { null }, + betas = setOf(StripeApiBeta.VippsPreviewV1), + ) + + networkRule.checkoutInit( + header("Stripe-Version", "2020-03-02;vipps_preview=v1"), + ) { response -> + response.testBodyFromFile("checkout-session-init.json") + } + + val result = repository.init( + sessionId = DEFAULT_CHECKOUT_SESSION_ID, + adaptivePricingAllowed = true, + ) + + assertThat(result.isSuccess).isTrue() + } + @Test fun `updateCurrency sends currency code and returns response on success`() = runTest { networkRule.checkoutUpdate( diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt index 2d5a3418579..c15a725c320 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt @@ -3,8 +3,10 @@ package com.stripe.android.paymentsheet.repositories import com.google.common.truth.Truth.assertThat import com.stripe.android.ApiKeyFixtures import com.stripe.android.PaymentConfiguration +import com.stripe.android.StripeApiBeta import com.stripe.android.core.Logger import com.stripe.android.core.networking.ApiRequest +import com.stripe.android.model.Customer import com.stripe.android.model.ListPaymentMethodsParams import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethodFixtures @@ -23,6 +25,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.anyString import org.mockito.Mockito.verify import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -332,6 +335,43 @@ internal class CustomerRepositoryTest { ) } + @Test + fun `retrieveCustomer() forwards configured betas in request options`() = runTest { + val repository = CustomerApiRepository( + stripeRepository = stripeRepository, + lazyPaymentConfig = { + PaymentConfiguration( + ApiKeyFixtures.FAKE_PUBLISHABLE_KEY, + "acct_123", + betas = setOf(StripeApiBeta.VippsPreviewV1), + ) + }, + logger = Logger.getInstance(false), + errorReporter = errorReporter, + workContext = testDispatcher, + ) + val optionsCaptor = argumentCaptor() + whenever( + stripeRepository.retrieveCustomer( + customerId = any(), + productUsageTokens = any(), + requestOptions = any(), + ) + ).thenReturn(Result.success(mock())) + + repository.retrieveCustomer( + customerId = FAKE_CUSTOMER_ID, + ephemeralKeySecret = FAKE_EPHEMERAL_KEY, + ) + + verify(stripeRepository).retrieveCustomer( + customerId = eq(FAKE_CUSTOMER_ID), + productUsageTokens = any(), + requestOptions = optionsCaptor.capture(), + ) + assertThat(optionsCaptor.firstValue.betas).containsExactly(StripeApiBeta.VippsPreviewV1) + } + @Test fun `detachPaymentMethod() should return payment method on success`() = runTest { diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/DefaultSavedPaymentMethodRepositoryTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/DefaultSavedPaymentMethodRepositoryTest.kt index d7f0f63f180..20f313542fd 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/DefaultSavedPaymentMethodRepositoryTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/DefaultSavedPaymentMethodRepositoryTest.kt @@ -322,6 +322,7 @@ class DefaultSavedPaymentMethodRepositoryTest { stripeNetworkClient = DefaultStripeNetworkClient(), publishableKeyProvider = { "pk_test_123" }, stripeAccountIdProvider = { null }, + betas = emptySet(), ) val repository = DefaultSavedPaymentMethodRepository( customerRepository = customerRepository, diff --git a/stripe-core/api/stripe-core.api b/stripe-core/api/stripe-core.api index 0baf79815a0..74a2b19aaf6 100644 --- a/stripe-core/api/stripe-core.api +++ b/stripe-core/api/stripe-core.api @@ -27,6 +27,15 @@ public final class com/stripe/android/core/BuildConfig { public fun ()V } +public final class com/stripe/android/StripeApiBeta : java/lang/Enum { + public static final field VippsPreviewV1 Lcom/stripe/android/StripeApiBeta; + public static final field WeChatPayV1 Lcom/stripe/android/StripeApiBeta; + public final fun getCode ()Ljava/lang/String; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/stripe/android/StripeApiBeta; + public static fun values ()[Lcom/stripe/android/StripeApiBeta; +} + public final class com/stripe/android/core/Logger$Companion { public final fun noop ()Lcom/stripe/android/core/Logger; public final fun real ()Lcom/stripe/android/core/Logger; @@ -269,4 +278,3 @@ public final class com/stripe/android/core/version/StripeSdkVersion { public static final field VERSION Ljava/lang/String; public static final field VERSION_NAME Ljava/lang/String; } - diff --git a/payments-core/src/main/java/com/stripe/android/StripeApiBeta.kt b/stripe-core/src/main/java/com/stripe/android/StripeApiBeta.kt similarity index 66% rename from payments-core/src/main/java/com/stripe/android/StripeApiBeta.kt rename to stripe-core/src/main/java/com/stripe/android/StripeApiBeta.kt index 92e560f2971..6b36032af53 100644 --- a/payments-core/src/main/java/com/stripe/android/StripeApiBeta.kt +++ b/stripe-core/src/main/java/com/stripe/android/StripeApiBeta.kt @@ -4,5 +4,6 @@ package com.stripe.android * Enums of beta headers allowed to be override when initializing [Stripe]. */ enum class StripeApiBeta(val code: String) { - WeChatPayV1("wechat_pay_beta=v1") + WeChatPayV1("wechat_pay_beta=v1"), + VippsPreviewV1("vipps_preview=v1"), } diff --git a/stripe-core/src/main/java/com/stripe/android/core/injection/NamedConstants.kt b/stripe-core/src/main/java/com/stripe/android/core/injection/NamedConstants.kt index 70064ce3e3d..d59f5f58373 100644 --- a/stripe-core/src/main/java/com/stripe/android/core/injection/NamedConstants.kt +++ b/stripe-core/src/main/java/com/stripe/android/core/injection/NamedConstants.kt @@ -20,6 +20,12 @@ const val PUBLISHABLE_KEY = "publishableKey" @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) const val STRIPE_ACCOUNT_ID = "stripeAccountId" +/** + * Name for user's configured Stripe API betas + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +const val STRIPE_API_BETAS = "stripeApiBetas" + /** * Name for form initial values */ diff --git a/stripe-core/src/main/java/com/stripe/android/core/networking/ApiRequest.kt b/stripe-core/src/main/java/com/stripe/android/core/networking/ApiRequest.kt index b76b55c6acc..3b86da47004 100644 --- a/stripe-core/src/main/java/com/stripe/android/core/networking/ApiRequest.kt +++ b/stripe-core/src/main/java/com/stripe/android/core/networking/ApiRequest.kt @@ -2,11 +2,13 @@ package com.stripe.android.core.networking import android.os.Parcelable import androidx.annotation.RestrictTo +import com.stripe.android.StripeApiBeta import com.stripe.android.core.ApiKeyValidator import com.stripe.android.core.ApiVersion import com.stripe.android.core.AppInfo import com.stripe.android.core.exception.InvalidRequestException import com.stripe.android.core.injection.PUBLISHABLE_KEY +import com.stripe.android.core.injection.STRIPE_API_BETAS import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID import com.stripe.android.core.version.StripeSdkVersion import kotlinx.parcelize.Parcelize @@ -14,7 +16,7 @@ import java.io.OutputStream import java.io.UnsupportedEncodingException import javax.inject.Inject import javax.inject.Named -import javax.inject.Provider +import kotlin.jvm.JvmSuppressWildcards /** * A class representing a Stripe API or Analytics request. @@ -102,7 +104,8 @@ data class ApiRequest internal constructor( data class Options constructor( val apiKey: String, val stripeAccount: String? = null, - val idempotencyKey: String? = null + val idempotencyKey: String? = null, + val betas: Set = emptySet(), ) : Parcelable { override fun toString(): String { @@ -125,10 +128,12 @@ data class ApiRequest internal constructor( @Inject constructor( @Named(PUBLISHABLE_KEY) publishableKeyProvider: () -> String, - @Named(STRIPE_ACCOUNT_ID) stripeAccountIdProvider: () -> String? + @Named(STRIPE_ACCOUNT_ID) stripeAccountIdProvider: () -> String?, + @Named(STRIPE_API_BETAS) betas: Set<@JvmSuppressWildcards StripeApiBeta>, ) : this( apiKey = publishableKeyProvider(), - stripeAccount = stripeAccountIdProvider() + stripeAccount = stripeAccountIdProvider(), + betas = betas, ) init { diff --git a/stripe-core/src/main/java/com/stripe/android/core/networking/RequestHeadersFactory.kt b/stripe-core/src/main/java/com/stripe/android/core/networking/RequestHeadersFactory.kt index c14ac9ce7e6..29075f7fff3 100644 --- a/stripe-core/src/main/java/com/stripe/android/core/networking/RequestHeadersFactory.kt +++ b/stripe-core/src/main/java/com/stripe/android/core/networking/RequestHeadersFactory.kt @@ -88,7 +88,7 @@ sealed class RequestHeadersFactory { val apiRequestOptions = optionsProvider() return mapOf( HEADER_ACCEPT to "application/json", - HEADER_STRIPE_VERSION to apiVersion, + HEADER_STRIPE_VERSION to createStripeVersion(apiRequestOptions), HEADER_AUTHORIZATION to "Bearer ${apiRequestOptions.apiKey}" ).plus( stripeClientUserAgentHeaderFactory.create(appInfo) @@ -111,6 +111,26 @@ sealed class RequestHeadersFactory { languageTag?.let { mapOf(HEADER_ACCEPT_LANGUAGE to it) }.orEmpty() ) } + + private fun createStripeVersion(options: ApiRequest.Options): String { + if (options.betas.isEmpty()) { + return apiVersion + } + + val version = apiVersion.substringBefore(';') + val configuredBetas = apiVersion + .substringAfter(';', "") + .split(';') + .filter { it.isNotBlank() } + .toCollection(linkedSetOf()) + + configuredBetas.addAll(options.betas.map { it.code }) + + return ApiVersion( + version = version, + betaCodes = configuredBetas, + ).code + } } /** diff --git a/stripe-core/src/test/java/com/stripe/android/core/networking/RequestHeadersFactoriesTest.kt b/stripe-core/src/test/java/com/stripe/android/core/networking/RequestHeadersFactoriesTest.kt index 713506ca6ad..b3fa790ad19 100644 --- a/stripe-core/src/test/java/com/stripe/android/core/networking/RequestHeadersFactoriesTest.kt +++ b/stripe-core/src/test/java/com/stripe/android/core/networking/RequestHeadersFactoriesTest.kt @@ -2,6 +2,7 @@ package com.stripe.android.core.networking import android.os.Build import com.google.common.truth.Truth.assertThat +import com.stripe.android.StripeApiBeta import com.stripe.android.core.ApiKeyFixtures import com.stripe.android.core.ApiVersion import com.stripe.android.core.AppInfo @@ -59,6 +60,20 @@ class RequestHeadersFactoriesTest { assertThat(headers.containsKey(HEADER_AUTHORIZATION)).isTrue() } + @Test + fun headers_withBetaRequestOptions_mergeRequestBetasIntoStripeVersion() { + val headers = createBasePaymentApiHeaders( + options = ApiRequest.Options( + apiKey = ApiKeyFixtures.FAKE_PUBLISHABLE_KEY, + betas = setOf(StripeApiBeta.VippsPreviewV1), + ) + ) + + assertThat(headers[HEADER_STRIPE_VERSION]).isEqualTo( + "${ApiVersion.get().code};vipps_preview=v1" + ) + } + @Test fun headers_containsPropertyMapValues() { val headers = createBasePaymentApiHeaders()