From 14260a3125d84879309b9a35787df94473b3e473 Mon Sep 17 00:00:00 2001 From: Till Hellmund Date: Tue, 3 Dec 2024 16:42:14 -0500 Subject: [PATCH] Pass incentive information to IBP flow (#9715) --- ...FinancialConnectionsPlaygroundViewModel.kt | 3 +- .../api/financial-connections.api | 24 ----- financial-connections/detekt-baseline.xml | 1 + .../FinancialConnectionsSheet.kt | 25 +---- .../HostedAuthUrlBuilder.kt | 5 + ...ialConnectionsConsumerSessionRepository.kt | 3 +- .../FinancialConnectionsSheetViewModelTest.kt | 93 +++++++++++++++++-- .../RealCreateInstantDebitsResultTest.kt | 4 +- .../NetworkingLinkSignupViewModelTest.kt | 3 +- ...ctionsConsumerSessionRepositoryImplTest.kt | 23 ++--- .../link/repositories/LinkApiRepository.kt | 3 +- .../repositories/LinkApiRepositoryTest.kt | 9 +- payments-model/api/payments-model.api | 24 +++++ .../model/IncentiveEligibilitySession.kt | 33 +++++++ .../android/repository/ConsumersApiService.kt | 15 +-- .../repository/ConsumersApiServiceImplTest.kt | 4 +- .../ach/USBankAccountFormViewModel.kt | 20 ++-- .../ach/USBankAccountFormViewModelTest.kt | 8 +- 18 files changed, 189 insertions(+), 111 deletions(-) create mode 100644 payments-model/src/main/java/com/stripe/android/model/IncentiveEligibilitySession.kt diff --git a/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt b/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt index f1cf5283b23..a63255e0bfb 100644 --- a/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt +++ b/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt @@ -12,7 +12,6 @@ import com.stripe.android.confirmPaymentIntent import com.stripe.android.financialconnections.FinancialConnections import com.stripe.android.financialconnections.FinancialConnectionsSheet import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext -import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext.InitializationMode import com.stripe.android.financialconnections.FinancialConnectionsSheetForTokenResult import com.stripe.android.financialconnections.FinancialConnectionsSheetResult import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent @@ -133,7 +132,6 @@ internal class FinancialConnectionsPlaygroundViewModel( ephemeralKey = it.ephemeralKey, customerId = it.customerId, elementsSessionContext = ElementsSessionContext( - initializationMode = InitializationMode.PaymentIntent(it.intentId), amount = it.amount, currency = it.currency, linkMode = LinkMode.LinkPaymentMethod, @@ -145,6 +143,7 @@ internal class FinancialConnectionsPlaygroundViewModel( phone = null, phoneCountryCode = null, ), + incentiveEligibilitySession = null, ), experience = settings.get().selectedOption, integrationType = settings.get().selectedOption, diff --git a/financial-connections/api/financial-connections.api b/financial-connections/api/financial-connections.api index f45f3765cdc..9eaf25586bf 100644 --- a/financial-connections/api/financial-connections.api +++ b/financial-connections/api/financial-connections.api @@ -169,30 +169,6 @@ public final class com/stripe/android/financialconnections/FinancialConnectionsS public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class com/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$DeferredIntent$Creator : android/os/Parcelable$Creator { - public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$DeferredIntent; - public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$DeferredIntent; - public synthetic fun newArray (I)[Ljava/lang/Object; -} - -public final class com/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$PaymentIntent$Creator : android/os/Parcelable$Creator { - public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$PaymentIntent; - public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$PaymentIntent; - public synthetic fun newArray (I)[Ljava/lang/Object; -} - -public final class com/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$SetupIntent$Creator : android/os/Parcelable$Creator { - public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$SetupIntent; - public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$InitializationMode$SetupIntent; - public synthetic fun newArray (I)[Ljava/lang/Object; -} - public final class com/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$PrefillDetails$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$PrefillDetails; diff --git a/financial-connections/detekt-baseline.xml b/financial-connections/detekt-baseline.xml index 27693e15dfc..9a70a986a90 100644 --- a/financial-connections/detekt-baseline.xml +++ b/financial-connections/detekt-baseline.xml @@ -4,6 +4,7 @@ ConstructorParameterNaming:FinancialConnectionsAuthorizationSession.kt$FinancialConnectionsAuthorizationSession$@SerialName(value = "is_oauth") private val _isOAuth: Boolean? = false ConstructorParameterNaming:PartnerAccountsList.kt$PartnerAccount$@SerialName(value = "allow_selection") private val _allowSelection: Boolean? = null + LargeClass:FinancialConnectionsSheetViewModelTest.kt$FinancialConnectionsSheetViewModelTest LongMethod:AccountItem.kt$@Composable @Preview internal fun AccountItemPreview() LongMethod:Button.kt$@Composable internal fun FinancialConnectionsButton( onClick: () -> Unit, modifier: Modifier = Modifier, type: Type = Primary, size: FinancialConnectionsButton.Size = FinancialConnectionsButton.Size.Regular, enabled: Boolean = true, loading: Boolean = false, content: @Composable (RowScope.() -> Unit) ) LongMethod:FinancialConnectionsSheetNativeActivity.kt$FinancialConnectionsSheetNativeActivity$@Composable fun NavHost( initialPane: Pane, testMode: Boolean, ) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheet.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheet.kt index f5fb6ee158c..57aa93b6dcb 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheet.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheet.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import com.stripe.android.financialconnections.launcher.FinancialConnectionsSheetForDataLauncher import com.stripe.android.financialconnections.launcher.FinancialConnectionsSheetForTokenLauncher import com.stripe.android.financialconnections.launcher.FinancialConnectionsSheetLauncher +import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.LinkMode import kotlinx.parcelize.Parcelize @@ -42,36 +43,14 @@ class FinancialConnectionsSheet internal constructor( @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Parcelize data class ElementsSessionContext( - val initializationMode: InitializationMode, val amount: Long?, val currency: String?, val linkMode: LinkMode?, val billingDetails: BillingDetails?, val prefillDetails: PrefillDetails, + val incentiveEligibilitySession: IncentiveEligibilitySession? ) : Parcelable { - val paymentIntentId: String? - get() = (initializationMode as? InitializationMode.PaymentIntent)?.paymentIntentId - - val setupIntentId: String? - get() = (initializationMode as? InitializationMode.SetupIntent)?.setupIntentId - - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - sealed interface InitializationMode : Parcelable { - - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Parcelize - data class PaymentIntent(val paymentIntentId: String) : InitializationMode - - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Parcelize - data class SetupIntent(val setupIntentId: String) : InitializationMode - - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Parcelize - data object DeferredIntent : InitializationMode - } - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Parcelize data class BillingDetails( diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/HostedAuthUrlBuilder.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/HostedAuthUrlBuilder.kt index bc636d86442..a0c8f392501 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/HostedAuthUrlBuilder.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/HostedAuthUrlBuilder.kt @@ -6,6 +6,7 @@ import com.stripe.android.financialconnections.FinancialConnectionsSheet.Element import com.stripe.android.financialconnections.launcher.FinancialConnectionsSheetActivityArgs import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest import com.stripe.android.financialconnections.utils.toApiParams +import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.LinkMode internal object HostedAuthUrlBuilder { @@ -20,6 +21,7 @@ internal object HostedAuthUrlBuilder { linkMode = args.elementsSessionContext?.linkMode, billingDetails = args.elementsSessionContext?.billingDetails, prefillDetails = args.elementsSessionContext?.prefillDetails, + incentiveEligibilitySession = args.elementsSessionContext?.incentiveEligibilitySession, ) } @@ -29,6 +31,7 @@ internal object HostedAuthUrlBuilder { linkMode: LinkMode?, billingDetails: BillingDetails?, prefillDetails: PrefillDetails?, + incentiveEligibilitySession: IncentiveEligibilitySession?, ): String? { if (hostedAuthUrl == null) { return null @@ -40,8 +43,10 @@ internal object HostedAuthUrlBuilder { // takes place on the web side of the flow and the payment method ID is returned to the app. queryParams.add("return_payment_method=true") queryParams.add("expand_payment_method=true") + queryParams.add("instantDebitsIncentive=${incentiveEligibilitySession != null}") linkMode?.let { queryParams.add("link_mode=${it.value}") } billingDetails?.let { queryParams.add(makeBillingDetailsQueryParams(it)) } + incentiveEligibilitySession?.let { queryParams.add("incentiveEligibilitySession=${it.id}") } } prefillDetails?.run { diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt index e6a33f2f205..80378469a0e 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt @@ -145,8 +145,7 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl( locale = locale, amount = elementsSessionContext?.amount, currency = elementsSessionContext?.currency, - paymentIntentId = null, - setupIntentId = null, + incentiveEligibilitySession = elementsSessionContext?.incentiveEligibilitySession, requestOptions = provideApiRequestOptions(useConsumerPublishableKey = false), requestSurface = requestSurface, consentAction = EnteredPhoneNumberClickedSaveToLink, diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt index 659b8099b69..ed07b47973a 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt @@ -34,6 +34,7 @@ import com.stripe.android.financialconnections.model.FinancialConnectionsAccount import com.stripe.android.financialconnections.model.FinancialConnectionsSession import com.stripe.android.financialconnections.model.FinancialConnectionsSession.StatusDetails import com.stripe.android.financialconnections.presentation.withState +import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.LinkMode import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -147,7 +148,6 @@ class FinancialConnectionsSheetViewModelTest { initialArgs = ForInstantDebits( configuration = configuration, elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("pi_123"), amount = 123, currency = "usd", linkMode = LinkMode.LinkPaymentMethod, @@ -156,7 +156,8 @@ class FinancialConnectionsSheetViewModelTest { email = null, phone = null, phoneCountryCode = null, - ) + ), + incentiveEligibilitySession = null, ), ) ) @@ -183,7 +184,6 @@ class FinancialConnectionsSheetViewModelTest { initialArgs = ForInstantDebits( configuration = configuration, elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("pi_123"), amount = 123, currency = "usd", linkMode = LinkMode.LinkPaymentMethod, @@ -192,7 +192,8 @@ class FinancialConnectionsSheetViewModelTest { email = null, phone = null, phoneCountryCode = null, - ) + ), + incentiveEligibilitySession = null, ), ) ) @@ -218,7 +219,6 @@ class FinancialConnectionsSheetViewModelTest { initialArgs = ForInstantDebits( configuration = configuration, elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("pi_123"), amount = 123, currency = "usd", linkMode = null, @@ -227,7 +227,8 @@ class FinancialConnectionsSheetViewModelTest { email = null, phone = null, phoneCountryCode = null, - ) + ), + incentiveEligibilitySession = null, ), ) ) @@ -240,6 +241,78 @@ class FinancialConnectionsSheetViewModelTest { } } + @Test + fun `init - when instant debits flow, hosted auth url contains incentive info if eligible`() = runTest { + // Given + whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) + whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) + + // When + val viewModel = createViewModel( + defaultInitialState.copy( + initialArgs = ForInstantDebits( + configuration = configuration, + elementsSessionContext = ElementsSessionContext( + amount = 123, + currency = "usd", + linkMode = null, + billingDetails = null, + prefillDetails = ElementsSessionContext.PrefillDetails( + email = null, + phone = null, + phoneCountryCode = null, + ), + incentiveEligibilitySession = IncentiveEligibilitySession.PaymentIntent("pi_123"), + ), + ) + ) + ) + + // Then + withState(viewModel) { + val viewEffect = it.viewEffect as OpenAuthFlowWithUrl + assertThat(viewEffect.url).contains("instantDebitsIncentive=true") + assertThat(viewEffect.url).contains("incentiveEligibilitySession=pi_123") + } + } + + @Test + fun `init - when instant debits flow, hosted auth url does not contain incentive info if not eligible`() = runTest { + // Given + whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) + whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) + + // When + val viewModel = createViewModel( + defaultInitialState.copy( + initialArgs = ForInstantDebits( + configuration = configuration, + elementsSessionContext = ElementsSessionContext( + amount = 123, + currency = "usd", + linkMode = null, + billingDetails = null, + prefillDetails = ElementsSessionContext.PrefillDetails( + email = null, + phone = null, + phoneCountryCode = null, + ), + incentiveEligibilitySession = null, + ), + ) + ) + ) + + // Then + withState(viewModel) { + val viewEffect = it.viewEffect as OpenAuthFlowWithUrl + assertThat(viewEffect.url).contains("instantDebitsIncentive=false") + assertThat(viewEffect.url).doesNotContain("incentiveEligibilitySession") + } + } + @Test fun `init - hosted auth url contains prefill details`() = runTest { // Given @@ -253,7 +326,6 @@ class FinancialConnectionsSheetViewModelTest { initialArgs = ForInstantDebits( configuration = configuration, elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("pi_123"), amount = 123, currency = "usd", linkMode = null, @@ -262,7 +334,8 @@ class FinancialConnectionsSheetViewModelTest { email = "email@email.com", phone = "5555551234", phoneCountryCode = "US", - ) + ), + incentiveEligibilitySession = null, ), ) ) @@ -290,7 +363,6 @@ class FinancialConnectionsSheetViewModelTest { initialArgs = ForInstantDebits( configuration = configuration, elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("pi_123"), amount = 123, currency = "usd", linkMode = null, @@ -311,7 +383,8 @@ class FinancialConnectionsSheetViewModelTest { email = null, phone = null, phoneCountryCode = null, - ) + ), + incentiveEligibilitySession = null, ), ) ) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/domain/RealCreateInstantDebitsResultTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/domain/RealCreateInstantDebitsResultTest.kt index 1c1cd6a8e83..57852fabf23 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/domain/RealCreateInstantDebitsResultTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/domain/RealCreateInstantDebitsResultTest.kt @@ -214,7 +214,6 @@ class RealCreateInstantDebitsResultTest { billingDetails: BillingDetails? = null, ): ElementsSessionContext { return ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("pi_123"), amount = 100L, currency = "usd", linkMode = linkMode, @@ -223,7 +222,8 @@ class RealCreateInstantDebitsResultTest { email = null, phone = null, phoneCountryCode = null, - ) + ), + incentiveEligibilitySession = null, ) } } diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt index c76f3157911..f1805ed6c10 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt @@ -12,7 +12,6 @@ import com.stripe.android.financialconnections.ApiKeyFixtures.sessionManifest import com.stripe.android.financialconnections.ApiKeyFixtures.syncResponse import com.stripe.android.financialconnections.CoroutineTestRule import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext -import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext.InitializationMode import com.stripe.android.financialconnections.TestFinancialConnectionsAnalyticsTracker import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.ConsentAgree.analyticsValue import com.stripe.android.financialconnections.domain.GetCachedAccounts @@ -122,7 +121,6 @@ class NetworkingLinkSignupViewModelTest { val viewModel = buildViewModel( state = NetworkingLinkSignupState(), elementsSessionContext = ElementsSessionContext( - initializationMode = InitializationMode.PaymentIntent("pi_1234"), amount = null, currency = null, linkMode = LinkMode.LinkPaymentMethod, @@ -132,6 +130,7 @@ class NetworkingLinkSignupViewModelTest { phone = "5555555555", phoneCountryCode = "US", ), + incentiveEligibilitySession = null, ) ) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepositoryImplTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepositoryImplTest.kt index 22175ddab00..9053aceebfc 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepositoryImplTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepositoryImplTest.kt @@ -11,7 +11,6 @@ import com.stripe.android.financialconnections.ApiKeyFixtures.consumerSession import com.stripe.android.financialconnections.ApiKeyFixtures.consumerSessionSignup import com.stripe.android.financialconnections.ApiKeyFixtures.verifiedConsumerSession import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext -import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext.InitializationMode import com.stripe.android.financialconnections.repository.api.FinancialConnectionsConsumersApiService import com.stripe.android.model.ConsumerSession import com.stripe.android.model.ConsumerSession.VerificationSession.SessionState @@ -19,6 +18,7 @@ import com.stripe.android.model.ConsumerSession.VerificationSession.SessionType import com.stripe.android.model.ConsumerSessionLookup import com.stripe.android.model.ConsumerSessionSignup import com.stripe.android.model.CustomEmailType +import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.LinkMode import com.stripe.android.model.SharePaymentDetails import com.stripe.android.model.VerificationType @@ -28,7 +28,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq -import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -89,8 +88,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { locale = anyOrNull(), amount = anyOrNull(), currency = anyOrNull(), - paymentIntentId = anyOrNull(), - setupIntentId = anyOrNull(), + incentiveEligibilitySession = anyOrNull(), consentAction = anyOrNull(), requestSurface = anyOrNull(), requestOptions = anyOrNull(), @@ -147,8 +145,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { locale = anyOrNull(), amount = eq(1234), currency = eq("cad"), - paymentIntentId = isNull(), - setupIntentId = isNull(), + incentiveEligibilitySession = eq(IncentiveEligibilitySession.PaymentIntent("pi_123")), consentAction = anyOrNull(), requestSurface = anyOrNull(), requestOptions = anyOrNull(), @@ -159,7 +156,6 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { val repository = buildRepository( elementsSessionContext = ElementsSessionContext( - initializationMode = InitializationMode.PaymentIntent("pi_123"), amount = 1234, currency = "cad", linkMode = LinkMode.LinkPaymentMethod, @@ -169,6 +165,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { phone = null, phoneCountryCode = null, ), + incentiveEligibilitySession = IncentiveEligibilitySession.PaymentIntent("pi_123"), ) ) @@ -341,8 +338,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { locale = anyOrNull(), amount = anyOrNull(), currency = anyOrNull(), - paymentIntentId = anyOrNull(), - setupIntentId = anyOrNull(), + incentiveEligibilitySession = anyOrNull(), consentAction = anyOrNull(), requestSurface = anyOrNull(), requestOptions = anyOrNull(), @@ -363,8 +359,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { locale = anyOrNull(), amount = anyOrNull(), currency = anyOrNull(), - paymentIntentId = anyOrNull(), - setupIntentId = anyOrNull(), + incentiveEligibilitySession = anyOrNull(), requestSurface = eq("android_connections"), consentAction = anyOrNull(), requestOptions = anyOrNull(), @@ -384,8 +379,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { locale = anyOrNull(), amount = anyOrNull(), currency = anyOrNull(), - paymentIntentId = anyOrNull(), - setupIntentId = anyOrNull(), + incentiveEligibilitySession = anyOrNull(), consentAction = anyOrNull(), requestSurface = anyOrNull(), requestOptions = anyOrNull(), @@ -406,8 +400,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest { locale = anyOrNull(), amount = anyOrNull(), currency = anyOrNull(), - paymentIntentId = anyOrNull(), - setupIntentId = anyOrNull(), + incentiveEligibilitySession = anyOrNull(), requestSurface = eq("android_instant_debits"), consentAction = anyOrNull(), requestOptions = anyOrNull(), diff --git a/link/src/main/java/com/stripe/android/link/repositories/LinkApiRepository.kt b/link/src/main/java/com/stripe/android/link/repositories/LinkApiRepository.kt index 434bb3bc0b7..c14299199ee 100644 --- a/link/src/main/java/com/stripe/android/link/repositories/LinkApiRepository.kt +++ b/link/src/main/java/com/stripe/android/link/repositories/LinkApiRepository.kt @@ -67,8 +67,7 @@ internal class LinkApiRepository @Inject constructor( locale = locale, amount = null, currency = null, - paymentIntentId = null, - setupIntentId = null, + incentiveEligibilitySession = null, consentAction = consentAction, requestOptions = buildRequestOptions(), requestSurface = REQUEST_SURFACE, diff --git a/link/src/test/java/com/stripe/android/link/repositories/LinkApiRepositoryTest.kt b/link/src/test/java/com/stripe/android/link/repositories/LinkApiRepositoryTest.kt index 77730b6e42e..d56b33b684f 100644 --- a/link/src/test/java/com/stripe/android/link/repositories/LinkApiRepositoryTest.kt +++ b/link/src/test/java/com/stripe/android/link/repositories/LinkApiRepositoryTest.kt @@ -129,8 +129,7 @@ class LinkApiRepositoryTest { locale = Locale.US, amount = null, currency = null, - paymentIntentId = null, - setupIntentId = null, + incentiveEligibilitySession = null, requestSurface = "android_payment_element", consentAction = ConsumerSignUpConsentAction.Checkbox, requestOptions = ApiRequest.Options(PUBLISHABLE_KEY, STRIPE_ACCOUNT_ID), @@ -149,8 +148,7 @@ class LinkApiRepositoryTest { locale = anyOrNull(), amount = anyOrNull(), currency = anyOrNull(), - paymentIntentId = anyOrNull(), - setupIntentId = anyOrNull(), + incentiveEligibilitySession = anyOrNull(), requestSurface = any(), consentAction = any(), requestOptions = any() @@ -180,8 +178,7 @@ class LinkApiRepositoryTest { locale = anyOrNull(), amount = anyOrNull(), currency = anyOrNull(), - paymentIntentId = anyOrNull(), - setupIntentId = anyOrNull(), + incentiveEligibilitySession = anyOrNull(), requestSurface = any(), consentAction = any(), requestOptions = any() diff --git a/payments-model/api/payments-model.api b/payments-model/api/payments-model.api index 922cbdf6075..1b2ecf5cf89 100644 --- a/payments-model/api/payments-model.api +++ b/payments-model/api/payments-model.api @@ -371,6 +371,30 @@ public final class com/stripe/android/model/ConsumerSessionSignup$Creator : andr public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/model/IncentiveEligibilitySession$DeferredIntent$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/IncentiveEligibilitySession$DeferredIntent; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/model/IncentiveEligibilitySession$DeferredIntent; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/stripe/android/model/IncentiveEligibilitySession$PaymentIntent$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/IncentiveEligibilitySession$PaymentIntent; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/model/IncentiveEligibilitySession$PaymentIntent; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/stripe/android/model/IncentiveEligibilitySession$SetupIntent$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/IncentiveEligibilitySession$SetupIntent; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/model/IncentiveEligibilitySession$SetupIntent; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/model/LinkConsumerIncentive$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/LinkConsumerIncentive; diff --git a/payments-model/src/main/java/com/stripe/android/model/IncentiveEligibilitySession.kt b/payments-model/src/main/java/com/stripe/android/model/IncentiveEligibilitySession.kt new file mode 100644 index 00000000000..9faa93a19d7 --- /dev/null +++ b/payments-model/src/main/java/com/stripe/android/model/IncentiveEligibilitySession.kt @@ -0,0 +1,33 @@ +package com.stripe.android.model + +import android.os.Parcelable +import androidx.annotation.RestrictTo +import kotlinx.parcelize.Parcelize + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +sealed interface IncentiveEligibilitySession : Parcelable { + + val id: String + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Parcelize + data class PaymentIntent(override val id: String) : IncentiveEligibilitySession + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Parcelize + data class SetupIntent(override val id: String) : IncentiveEligibilitySession + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Parcelize + data class DeferredIntent(override val id: String) : IncentiveEligibilitySession + + fun toParamMap(): Map { + val key = when (this) { + is PaymentIntent -> "financial_incentive[payment_intent]" + is SetupIntent -> "financial_incentive[setup_intent]" + is DeferredIntent -> "financial_incentive[elements_session_id]" + } + + return mapOf(key to id) + } +} diff --git a/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt b/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt index a08fb37cbb9..343faa09b4e 100644 --- a/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt +++ b/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt @@ -16,6 +16,7 @@ import com.stripe.android.model.ConsumerSessionLookup import com.stripe.android.model.ConsumerSessionSignup import com.stripe.android.model.ConsumerSignUpConsentAction import com.stripe.android.model.CustomEmailType +import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.SharePaymentDetails import com.stripe.android.model.VerificationType import com.stripe.android.model.parsers.AttachConsumerToLinkAccountSessionJsonParser @@ -37,8 +38,7 @@ interface ConsumersApiService { locale: Locale?, amount: Long?, currency: String?, - paymentIntentId: String?, - setupIntentId: String?, + incentiveEligibilitySession: IncentiveEligibilitySession?, requestSurface: String, consentAction: ConsumerSignUpConsentAction, requestOptions: ApiRequest.Options, @@ -117,8 +117,7 @@ class ConsumersApiServiceImpl( locale: Locale?, amount: Long?, currency: String?, - paymentIntentId: String?, - setupIntentId: String?, + incentiveEligibilitySession: IncentiveEligibilitySession?, requestSurface: String, consentAction: ConsumerSignUpConsentAction, requestOptions: ApiRequest.Options, @@ -147,13 +146,7 @@ class ConsumersApiServiceImpl( mapOf("legal_name" to it) } ?: emptyMap() ).plus( - paymentIntentId?.let { - mapOf("financial_incentive[payment_intent]" to it) - }.orEmpty() - ).plus( - setupIntentId?.let { - mapOf("financial_incentive[setup_intent]" to it) - }.orEmpty() + incentiveEligibilitySession?.toParamMap().orEmpty() ), ), responseJsonParser = ConsumerSessionSignupJsonParser, diff --git a/payments-model/src/test/java/com/stripe/android/repository/ConsumersApiServiceImplTest.kt b/payments-model/src/test/java/com/stripe/android/repository/ConsumersApiServiceImplTest.kt index 94c56787779..2125799ba47 100644 --- a/payments-model/src/test/java/com/stripe/android/repository/ConsumersApiServiceImplTest.kt +++ b/payments-model/src/test/java/com/stripe/android/repository/ConsumersApiServiceImplTest.kt @@ -12,6 +12,7 @@ import com.stripe.android.model.ConsumerPaymentDetails import com.stripe.android.model.ConsumerPaymentDetailsCreateParams import com.stripe.android.model.ConsumerSession import com.stripe.android.model.ConsumerSignUpConsentAction +import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.VerificationType import com.stripe.android.networktesting.NetworkRule import com.stripe.android.networktesting.RequestMatchers.bodyPart @@ -67,8 +68,7 @@ class ConsumersApiServiceImplTest { locale = Locale.US, amount = 1234, currency = "cad", - paymentIntentId = "pi_123", - setupIntentId = null, + incentiveEligibilitySession = IncentiveEligibilitySession.PaymentIntent("pi_123"), consentAction = ConsumerSignUpConsentAction.Checkbox, requestSurface = requestSurface, requestOptions = DEFAULT_OPTIONS, diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt index 67d0aaba68e..ac00609691b 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt @@ -17,6 +17,7 @@ import com.stripe.android.financialconnections.FinancialConnectionsSheet.Element import com.stripe.android.financialconnections.model.BankAccount import com.stripe.android.financialconnections.model.FinancialConnectionsAccount import com.stripe.android.model.Address +import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.LinkMode import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethodCreateParams @@ -494,21 +495,28 @@ internal class USBankAccountFormViewModel @Inject internal constructor( } private fun makeElementsSessionContext(): ElementsSessionContext { - val initializationMode = if (args.clientSecret == null) { - ElementsSessionContext.InitializationMode.DeferredIntent - } else if (args.isPaymentFlow) { - ElementsSessionContext.InitializationMode.PaymentIntent(args.stripeIntentId!!) + val intentId = args.stripeIntentId!! + val eligibleForIncentive = args.incentive != null + + val incentiveEligibilitySession = if (eligibleForIncentive) { + if (args.clientSecret == null) { + IncentiveEligibilitySession.DeferredIntent(intentId) + } else if (args.isPaymentFlow) { + IncentiveEligibilitySession.PaymentIntent(intentId) + } else { + IncentiveEligibilitySession.SetupIntent(intentId) + } } else { - ElementsSessionContext.InitializationMode.SetupIntent(args.stripeIntentId!!) + null } return ElementsSessionContext( - initializationMode = initializationMode, amount = args.formArgs.amount?.value, currency = args.formArgs.amount?.currencyCode, linkMode = args.linkMode, billingDetails = makeElementsSessionContextBillingDetails(), prefillDetails = makePrefillDetails(), + incentiveEligibilitySession = incentiveEligibilitySession, ) } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModelTest.kt index ea8ced3c24d..7cc0e2bd35c 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModelTest.kt @@ -716,7 +716,6 @@ class USBankAccountFormViewModelTest { name = "Jenny Rose", email = "email@email.com", elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.DeferredIntent, amount = 5099, currency = "usd", linkMode = LinkMode.LinkPaymentMethod, @@ -729,6 +728,7 @@ class USBankAccountFormViewModelTest { phone = null, phoneCountryCode = "US", ), + incentiveEligibilitySession = null, ), ) ), @@ -765,7 +765,6 @@ class USBankAccountFormViewModelTest { name = "Jenny Rose", email = "email@email.com", elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.DeferredIntent, amount = null, currency = null, linkMode = LinkMode.LinkPaymentMethod, @@ -778,6 +777,7 @@ class USBankAccountFormViewModelTest { phone = null, phoneCountryCode = "US", ), + incentiveEligibilitySession = null, ), ) ), @@ -938,7 +938,6 @@ class USBankAccountFormViewModelTest { name = "Some Name", email = "email@email.com", elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("id_12345"), amount = 5099, currency = "usd", linkMode = null, @@ -951,6 +950,7 @@ class USBankAccountFormViewModelTest { phone = null, phoneCountryCode = "US", ), + incentiveEligibilitySession = null, ), ) ), @@ -980,7 +980,6 @@ class USBankAccountFormViewModelTest { CollectBankAccountConfiguration.InstantDebits( email = "email@email.com", elementsSessionContext = ElementsSessionContext( - initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("id_12345"), amount = 5099, currency = "usd", linkMode = LinkMode.LinkCardBrand, @@ -992,6 +991,7 @@ class USBankAccountFormViewModelTest { phone = null, phoneCountryCode = "US", ), + incentiveEligibilitySession = null, ), ) ),