From 186d1136981a2595ebbd46e562e56747ccd20f0e Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Wed, 24 Apr 2024 17:13:53 -0400 Subject: [PATCH 01/10] Add ability to set on_behalf_of for CBC elements --- .../android/networking/StripeApiRepository.kt | 3 ++- .../stripe/android/networking/StripeRepository.kt | 1 + .../java/com/stripe/android/view/CardFormView.kt | 13 +++++++++++++ .../com/stripe/android/view/CardInputWidget.kt | 14 ++++++++++++++ .../stripe/android/view/CardMultilineWidget.kt | 15 +++++++++++++++ .../stripe/android/view/CardWidgetViewModel.kt | 14 +++++++++++++- 6 files changed, 58 insertions(+), 2 deletions(-) diff --git a/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt b/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt index bee5a13a82e..9bd3aa7bd74 100644 --- a/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt +++ b/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt @@ -1460,12 +1460,13 @@ class StripeApiRepository @JvmOverloads internal constructor( override suspend fun retrieveCardElementConfig( requestOptions: ApiRequest.Options, + params: Map? ): Result { return fetchStripeModelResult( apiRequestFactory.createGet( url = mobileCardElementConfigUrl, options = requestOptions, - params = null, + params = params, ), jsonParser = MobileCardElementConfigParser(), ) diff --git a/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt b/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt index 2ca9e260687..2ed3ecd9483 100644 --- a/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt +++ b/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt @@ -394,6 +394,7 @@ interface StripeRepository { @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) suspend fun retrieveCardElementConfig( requestOptions: ApiRequest.Options, + params: Map? = null ): Result @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) diff --git a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt index 776e48c99d4..c459a809b7d 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt @@ -225,6 +225,19 @@ class CardFormView @JvmOverloads constructor( cardValidCallback?.onInputChanged(invalidFields.isEmpty(), invalidFields) } + /** + * The account (if any) for which the funds of the intent are intended. + * The Stripe account ID (if any) which is the business of record. + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. + * This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * provided on the Intent used when confirming payment. + */ + fun setOnBehalfOf(onBehalfOf: String) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = onBehalfOf + } + } + private fun setupCountryAndPostal() { // wire up postal code and country updatePostalCodeViewLocale(countryLayout.selectedCountryCode) diff --git a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt index 9134bb82e8c..5016932ec15 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt @@ -392,6 +392,7 @@ class CardInputWidget @JvmOverloads constructor( doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> viewModel.isCbcEligible.launchAndCollect { isCbcEligible -> + println("YEET CardInputWidget updating isCbcEligible with value: $isCbcEligible") cardBrandView.isCbcEligible = isCbcEligible } } @@ -485,6 +486,19 @@ class CardInputWidget @JvmOverloads constructor( cardBrandView.merchantPreferredNetworks = preferredNetworks } + /** + * The account (if any) for which the funds of the intent are intended. + * The Stripe account ID (if any) which is the business of record. + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. + * This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * provided on the Intent used when confirming payment. + */ + fun setOnBehalfOf(onBehalfOf: String) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = onBehalfOf + } + } + /** * Clear all text fields in the CardInputWidget. */ diff --git a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt index d22fb316f71..62c4be12b04 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt @@ -453,13 +453,28 @@ class CardMultilineWidget @JvmOverloads constructor( // see https://github.com/stripe/stripe-android/pull/3154 cvcEditText.hint = null + // do stuff doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> viewModel.isCbcEligible.launchAndCollect { isCbcEligible -> + println("YEET CardMultilineWidget updating isCbcEligible with value: $isCbcEligible") cardBrandView.isCbcEligible = isCbcEligible } } } + /** + * The account (if any) for which the funds of the intent are intended. + * The Stripe account ID (if any) which is the business of record. + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. + * This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * provided on the Intent used when confirming payment. + */ + fun setOnBehalfOf(onBehalfOf: String) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = onBehalfOf + } + } + /** * Clear all entered data and hide all error messages. */ diff --git a/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt b/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt index bdf1f5b72d1..7ac5035ab38 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt @@ -29,13 +29,22 @@ import javax.inject.Provider internal class CardWidgetViewModel( private val paymentConfigProvider: Provider, private val stripeRepository: StripeRepository, - dispatcher: CoroutineDispatcher = Dispatchers.IO, + private val dispatcher: CoroutineDispatcher = Dispatchers.IO, ) : ViewModel() { private val _isCbcEligible = MutableStateFlow(false) val isCbcEligible: StateFlow = _isCbcEligible + var onBehalfOf: String? = null + set(value) { + field = value + getEligibility() + } init { + getEligibility() + } + + private fun getEligibility() { viewModelScope.launch(dispatcher) { _isCbcEligible.value = determineCbcEligibility() } @@ -49,6 +58,9 @@ internal class CardWidgetViewModel( apiKey = paymentConfig.publishableKey, stripeAccount = paymentConfig.stripeAccountId, ), + params = onBehalfOf?.let { + mapOf("on_behalf_of" to it) + } ) val config = response.getOrNull() From de1b564f6215ecf9ea5e1fe97bc5289b768d1856 Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Wed, 24 Apr 2024 17:26:29 -0400 Subject: [PATCH 02/10] Remove logs --- .../src/main/java/com/stripe/android/view/CardInputWidget.kt | 1 - .../src/main/java/com/stripe/android/view/CardMultilineWidget.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt index 5016932ec15..e7230fdd28e 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt @@ -392,7 +392,6 @@ class CardInputWidget @JvmOverloads constructor( doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> viewModel.isCbcEligible.launchAndCollect { isCbcEligible -> - println("YEET CardInputWidget updating isCbcEligible with value: $isCbcEligible") cardBrandView.isCbcEligible = isCbcEligible } } diff --git a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt index 62c4be12b04..8a99d6adaec 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt @@ -456,7 +456,6 @@ class CardMultilineWidget @JvmOverloads constructor( // do stuff doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> viewModel.isCbcEligible.launchAndCollect { isCbcEligible -> - println("YEET CardMultilineWidget updating isCbcEligible with value: $isCbcEligible") cardBrandView.isCbcEligible = isCbcEligible } } From 9262921d95f8ca63b97816f896219e6f71544866 Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Thu, 25 Apr 2024 14:24:27 -0400 Subject: [PATCH 03/10] Fix tests --- .../stripe/android/testing/AbsFakeStripeRepository.kt | 1 + .../main/java/com/stripe/android/view/CardFormView.kt | 6 ++++-- .../java/com/stripe/android/view/CardInputWidget.kt | 6 ++++-- .../com/stripe/android/view/CardMultilineWidget.kt | 7 ++++--- .../com/stripe/android/view/CardWidgetViewModel.kt | 11 ++++++++++- .../com/stripe/android/utils/CardElementTestHelper.kt | 3 +++ .../android/utils/FakeCardElementConfigRepository.kt | 1 + .../com/stripe/android/view/CardNumberEditTextTest.kt | 2 ++ .../stripe/android/view/CardWidgetViewModelTest.kt | 4 ++++ 9 files changed, 33 insertions(+), 8 deletions(-) diff --git a/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt b/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt index 44fdf10b51f..ec626fcba74 100644 --- a/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt +++ b/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt @@ -431,6 +431,7 @@ abstract class AbsFakeStripeRepository : StripeRepository { override suspend fun retrieveCardElementConfig( requestOptions: ApiRequest.Options, + params: Map? ): Result { TODO("Not yet implemented") } diff --git a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt index c459a809b7d..f1b56c70631 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt @@ -233,8 +233,10 @@ class CardFormView @JvmOverloads constructor( * provided on the Intent used when confirming payment. */ fun setOnBehalfOf(onBehalfOf: String) { - doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> - viewModel.onBehalfOf = onBehalfOf + if (isAttachedToWindow) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = onBehalfOf + } } } diff --git a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt index e7230fdd28e..21d94cbf1fa 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt @@ -493,8 +493,10 @@ class CardInputWidget @JvmOverloads constructor( * provided on the Intent used when confirming payment. */ fun setOnBehalfOf(onBehalfOf: String) { - doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> - viewModel.onBehalfOf = onBehalfOf + if (isAttachedToWindow) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = onBehalfOf + } } } diff --git a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt index 8a99d6adaec..e39cab1ba2c 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt @@ -453,7 +453,6 @@ class CardMultilineWidget @JvmOverloads constructor( // see https://github.com/stripe/stripe-android/pull/3154 cvcEditText.hint = null - // do stuff doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> viewModel.isCbcEligible.launchAndCollect { isCbcEligible -> cardBrandView.isCbcEligible = isCbcEligible @@ -469,8 +468,10 @@ class CardMultilineWidget @JvmOverloads constructor( * provided on the Intent used when confirming payment. */ fun setOnBehalfOf(onBehalfOf: String) { - doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> - viewModel.onBehalfOf = onBehalfOf + if (isAttachedToWindow) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = onBehalfOf + } } } diff --git a/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt b/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt index 7ac5035ab38..469c3f454b3 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardWidgetViewModel.kt @@ -3,9 +3,11 @@ package com.stripe.android.view import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.createSavedStateHandle import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.lifecycle.lifecycleScope @@ -30,13 +32,15 @@ internal class CardWidgetViewModel( private val paymentConfigProvider: Provider, private val stripeRepository: StripeRepository, private val dispatcher: CoroutineDispatcher = Dispatchers.IO, + private val handle: SavedStateHandle ) : ViewModel() { private val _isCbcEligible = MutableStateFlow(false) val isCbcEligible: StateFlow = _isCbcEligible - var onBehalfOf: String? = null + var onBehalfOf: String? = handle[ON_BEHALF_OF] set(value) { field = value + handle[ON_BEHALF_OF] = value getEligibility() } @@ -81,9 +85,14 @@ internal class CardWidgetViewModel( return CardWidgetViewModel( paymentConfigProvider = { PaymentConfiguration.getInstance(context) }, stripeRepository = stripeRepository, + handle = extras.createSavedStateHandle() ) as T } } + + companion object { + internal const val ON_BEHALF_OF = "on_behalf_of" + } } internal fun View.doWithCardWidgetViewModel( diff --git a/payments-core/src/test/java/com/stripe/android/utils/CardElementTestHelper.kt b/payments-core/src/test/java/com/stripe/android/utils/CardElementTestHelper.kt index 2fe699e972d..b98530ec941 100644 --- a/payments-core/src/test/java/com/stripe/android/utils/CardElementTestHelper.kt +++ b/payments-core/src/test/java/com/stripe/android/utils/CardElementTestHelper.kt @@ -1,5 +1,6 @@ package com.stripe.android.utils +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import com.stripe.android.ApiKeyFixtures @@ -24,6 +25,7 @@ internal object CardElementTestHelper { stripeRepository = object : AbsFakeStripeRepository() { override suspend fun retrieveCardElementConfig( requestOptions: ApiRequest.Options, + params: Map? ): Result { return Result.success( MobileCardElementConfig( @@ -34,6 +36,7 @@ internal object CardElementTestHelper { ) } }, + handle = SavedStateHandle() ) val viewModelStore = ViewModelStore().apply { diff --git a/payments-core/src/test/java/com/stripe/android/utils/FakeCardElementConfigRepository.kt b/payments-core/src/test/java/com/stripe/android/utils/FakeCardElementConfigRepository.kt index e5d251133d0..1afa0aa8134 100644 --- a/payments-core/src/test/java/com/stripe/android/utils/FakeCardElementConfigRepository.kt +++ b/payments-core/src/test/java/com/stripe/android/utils/FakeCardElementConfigRepository.kt @@ -33,6 +33,7 @@ class FakeCardElementConfigRepository : AbsFakeStripeRepository() { override suspend fun retrieveCardElementConfig( requestOptions: ApiRequest.Options, + params: Map? ): Result { return channel.receive() } diff --git a/payments-core/src/test/java/com/stripe/android/view/CardNumberEditTextTest.kt b/payments-core/src/test/java/com/stripe/android/view/CardNumberEditTextTest.kt index ac7087a256c..e2a908abaf8 100644 --- a/payments-core/src/test/java/com/stripe/android/view/CardNumberEditTextTest.kt +++ b/payments-core/src/test/java/com/stripe/android/view/CardNumberEditTextTest.kt @@ -3,6 +3,7 @@ package com.stripe.android.view import android.text.TextWatcher import android.view.ViewGroup import androidx.appcompat.view.ContextThemeWrapper +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import androidx.test.core.app.ApplicationProvider @@ -1072,6 +1073,7 @@ internal class CardNumberEditTextTest { paymentConfigProvider = { PaymentConfiguration.getInstance(context) }, stripeRepository = repository, dispatcher = dispatcher, + handle = SavedStateHandle() ) val store = ViewModelStore().apply { diff --git a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt index f9f80f425cc..b09913a85f2 100644 --- a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt +++ b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt @@ -1,5 +1,6 @@ package com.stripe.android.view +import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import com.stripe.android.PaymentConfiguration @@ -26,6 +27,7 @@ class CardWidgetViewModelTest { paymentConfigProvider = { paymentConfig }, stripeRepository = stripeRepository, dispatcher = testDispatcher, + handle = SavedStateHandle() ) viewModel.isCbcEligible.test { @@ -44,6 +46,7 @@ class CardWidgetViewModelTest { paymentConfigProvider = { paymentConfig }, stripeRepository = stripeRepository, dispatcher = testDispatcher, + handle = SavedStateHandle() ) viewModel.isCbcEligible.test { @@ -61,6 +64,7 @@ class CardWidgetViewModelTest { paymentConfigProvider = { paymentConfig }, stripeRepository = stripeRepository, dispatcher = testDispatcher, + handle = SavedStateHandle() ) viewModel.isCbcEligible.test { From 953afb01319affaa67ba878a9b1b0c4b49ada751 Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Thu, 25 Apr 2024 14:43:37 -0400 Subject: [PATCH 04/10] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee312797e49..c2600fd6493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ## 20.41.1 - 2024-04-22 +### Payments +* [ADDED][8344](https://github.com/stripe/stripe-android/pull/8344) Added support for `onBehalfOf` to `CardInputWidget`, `CardMultilineWidget`, and `CardFormView`. This parameter may be required when setting a connected account as the merchant of record for a payment. For more information, see the [Connect docs](https://docs.stripe.com/connect/charges#on_behalf_of). + ### PaymentSheet * [FIXED][8297](https://github.com/stripe/stripe-android/pull/8297) Improve `PaymentSheet` and `CustomerSheet` loading user experience. From f5eb18e844b888d0831c8ab393ddbfc72334a6fe Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Thu, 25 Apr 2024 17:59:08 -0400 Subject: [PATCH 05/10] Add methods to baseline, formatting --- payments-core/api/payments-core.api | 3 +++ .../src/main/java/com/stripe/android/view/CardFormView.kt | 5 +++-- .../src/main/java/com/stripe/android/view/CardInputWidget.kt | 5 +++-- .../main/java/com/stripe/android/view/CardMultilineWidget.kt | 5 +++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/payments-core/api/payments-core.api b/payments-core/api/payments-core.api index 5d658f791c3..ade97f25f1f 100644 --- a/payments-core/api/payments-core.api +++ b/payments-core/api/payments-core.api @@ -7415,6 +7415,7 @@ public final class com/stripe/android/view/CardFormView : android/widget/LinearL public final fun getPaymentMethodCreateParams ()Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun setCardValidCallback (Lcom/stripe/android/view/CardValidCallback;)V public fun setEnabled (Z)V + public final fun setOnBehalfOf (Ljava/lang/String;)V public final fun setPreferredNetworks (Ljava/util/List;)V } @@ -7463,6 +7464,7 @@ public final class com/stripe/android/view/CardInputWidget : android/widget/Line public fun setEnabled (Z)V public fun setExpiryDate (II)V public fun setExpiryDateTextWatcher (Landroid/text/TextWatcher;)V + public final fun setOnBehalfOf (Ljava/lang/String;)V public final fun setPostalCodeEnabled (Z)V public final fun setPostalCodeRequired (Z)V public fun setPostalCodeTextWatcher (Landroid/text/TextWatcher;)V @@ -7499,6 +7501,7 @@ public final class com/stripe/android/view/CardMultilineWidget : android/widget/ public fun setEnabled (Z)V public fun setExpiryDate (II)V public fun setExpiryDateTextWatcher (Landroid/text/TextWatcher;)V + public final fun setOnBehalfOf (Ljava/lang/String;)V public final fun setPostalCodeRequired (Z)V public fun setPostalCodeTextWatcher (Landroid/text/TextWatcher;)V public final fun setPreferredNetworks (Ljava/util/List;)V diff --git a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt index f1b56c70631..720164be13f 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt @@ -228,8 +228,9 @@ class CardFormView @JvmOverloads constructor( /** * The account (if any) for which the funds of the intent are intended. * The Stripe account ID (if any) which is the business of record. - * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. - * This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant + * for your integration. This should match the + * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) * provided on the Intent used when confirming payment. */ fun setOnBehalfOf(onBehalfOf: String) { diff --git a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt index 21d94cbf1fa..e8456c07368 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt @@ -488,8 +488,9 @@ class CardInputWidget @JvmOverloads constructor( /** * The account (if any) for which the funds of the intent are intended. * The Stripe account ID (if any) which is the business of record. - * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. - * This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant + * for your integration. This should match the + * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) * provided on the Intent used when confirming payment. */ fun setOnBehalfOf(onBehalfOf: String) { diff --git a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt index e39cab1ba2c..af86ff4449f 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt @@ -463,8 +463,9 @@ class CardMultilineWidget @JvmOverloads constructor( /** * The account (if any) for which the funds of the intent are intended. * The Stripe account ID (if any) which is the business of record. - * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. - * This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant + * for your integration. This should match the + * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) * provided on the Intent used when confirming payment. */ fun setOnBehalfOf(onBehalfOf: String) { From c93325b5d131c911613e492b53098d89ee24c528 Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Thu, 25 Apr 2024 18:08:14 -0400 Subject: [PATCH 06/10] Add savedStateHandle test for CardWidgetViewModel --- .../android/view/CardWidgetViewModelTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt index b09913a85f2..5dd60cc85a5 100644 --- a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt +++ b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt @@ -73,4 +73,21 @@ class CardWidgetViewModelTest { expectNoEvents() } } + + @Test + fun `Saves OBO to savedStateHandle`() = runTest(testDispatcher) { + val stripeRepository = FakeCardElementConfigRepository() + val handle = SavedStateHandle() + + val viewModel = CardWidgetViewModel( + paymentConfigProvider = { paymentConfig }, + stripeRepository = stripeRepository, + dispatcher = testDispatcher, + handle = handle + ) + + viewModel.onBehalfOf = "test" + val obo: String? = handle["on_behalf_of"] + assertThat(obo).isEqualTo("test") + } } From 88f3031f58ff84d6246d7026f442518ab7c569ec Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Mon, 6 May 2024 17:19:34 -0400 Subject: [PATCH 07/10] Remove setOnBehalfOf function, add onBehalfOf var --- payments-core/api/payments-core.api | 3 ++ .../com/stripe/android/view/CardFormView.kt | 33 ++++++++++--------- .../stripe/android/view/CardInputWidget.kt | 33 ++++++++++--------- .../android/view/CardMultilineWidget.kt | 33 ++++++++++--------- 4 files changed, 54 insertions(+), 48 deletions(-) diff --git a/payments-core/api/payments-core.api b/payments-core/api/payments-core.api index ade97f25f1f..b580197f0c9 100644 --- a/payments-core/api/payments-core.api +++ b/payments-core/api/payments-core.api @@ -7412,6 +7412,7 @@ public final class com/stripe/android/view/CardFormView : android/widget/LinearL public synthetic fun (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getBrand ()Lcom/stripe/android/model/CardBrand; public final fun getCardParams ()Lcom/stripe/android/model/CardParams; + public final fun getOnBehalfOf ()Ljava/lang/String; public final fun getPaymentMethodCreateParams ()Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun setCardValidCallback (Lcom/stripe/android/view/CardValidCallback;)V public fun setEnabled (Z)V @@ -7446,6 +7447,7 @@ public final class com/stripe/android/view/CardInputWidget : android/widget/Line public fun clear ()V public final fun getBrand ()Lcom/stripe/android/model/CardBrand; public fun getCardParams ()Lcom/stripe/android/model/CardParams; + public final fun getOnBehalfOf ()Ljava/lang/String; public fun getPaymentMethodCard ()Lcom/stripe/android/model/PaymentMethodCreateParams$Card; public fun getPaymentMethodCreateParams ()Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun getPostalCodeEnabled ()Z @@ -7482,6 +7484,7 @@ public final class com/stripe/android/view/CardMultilineWidget : android/widget/ public fun clear ()V public final synthetic fun getBrand ()Lcom/stripe/android/model/CardBrand; public fun getCardParams ()Lcom/stripe/android/model/CardParams; + public final fun getOnBehalfOf ()Ljava/lang/String; public final fun getPaymentMethodBillingDetails ()Lcom/stripe/android/model/PaymentMethod$BillingDetails; public final fun getPaymentMethodBillingDetailsBuilder ()Lcom/stripe/android/model/PaymentMethod$BillingDetails$Builder; public fun getPaymentMethodCard ()Lcom/stripe/android/model/PaymentMethodCreateParams$Card; diff --git a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt index 720164be13f..9be7b98c504 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardFormView.kt @@ -183,6 +183,23 @@ class CardFormView @JvmOverloads constructor( val paymentMethodCreateParams: PaymentMethodCreateParams? get() = paymentMethodCard?.let { PaymentMethodCreateParams.create(it) } + /** + * The Stripe account ID (if any) which is the business of record. + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant + * for your integration. This should match the + * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * provided on the Intent used when confirming payment. + */ + var onBehalfOf: String? = null + set(value) { + if (isAttachedToWindow) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = value + } + } + field = value + } + init { orientation = VERTICAL @@ -225,22 +242,6 @@ class CardFormView @JvmOverloads constructor( cardValidCallback?.onInputChanged(invalidFields.isEmpty(), invalidFields) } - /** - * The account (if any) for which the funds of the intent are intended. - * The Stripe account ID (if any) which is the business of record. - * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant - * for your integration. This should match the - * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) - * provided on the Intent used when confirming payment. - */ - fun setOnBehalfOf(onBehalfOf: String) { - if (isAttachedToWindow) { - doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> - viewModel.onBehalfOf = onBehalfOf - } - } - } - private fun setupCountryAndPostal() { // wire up postal code and country updatePostalCodeViewLocale(countryLayout.selectedCountryCode) diff --git a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt index e8456c07368..976d4f9dc7d 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardInputWidget.kt @@ -345,6 +345,23 @@ class CardInputWidget @JvmOverloads constructor( updatePostalRequired() } + /** + * The Stripe account ID (if any) which is the business of record. + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant + * for your integration. This should match the + * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * provided on the Intent used when confirming payment. + */ + var onBehalfOf: String? = null + set(value) { + if (isAttachedToWindow) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = value + } + } + field = value + } + private fun updatePostalRequired() { if (isPostalRequired()) { requiredFields.add(postalCodeEditText) @@ -485,22 +502,6 @@ class CardInputWidget @JvmOverloads constructor( cardBrandView.merchantPreferredNetworks = preferredNetworks } - /** - * The account (if any) for which the funds of the intent are intended. - * The Stripe account ID (if any) which is the business of record. - * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant - * for your integration. This should match the - * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) - * provided on the Intent used when confirming payment. - */ - fun setOnBehalfOf(onBehalfOf: String) { - if (isAttachedToWindow) { - doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> - viewModel.onBehalfOf = onBehalfOf - } - } - } - /** * Clear all text fields in the CardInputWidget. */ diff --git a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt index af86ff4449f..43a155cc1e5 100644 --- a/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt +++ b/payments-core/src/main/java/com/stripe/android/view/CardMultilineWidget.kt @@ -211,6 +211,23 @@ class CardMultilineWidget @JvmOverloads constructor( null } + /** + * The Stripe account ID (if any) which is the business of record. + * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant + * for your integration. This should match the + * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + * provided on the Intent used when confirming payment. + */ + var onBehalfOf: String? = null + set(value) { + if (isAttachedToWindow) { + doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> + viewModel.onBehalfOf = value + } + } + field = value + } + /** * A [CardParams] representing the card details and postal code if all fields are valid; * otherwise `null` @@ -460,22 +477,6 @@ class CardMultilineWidget @JvmOverloads constructor( } } - /** - * The account (if any) for which the funds of the intent are intended. - * The Stripe account ID (if any) which is the business of record. - * See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant - * for your integration. This should match the - * [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) - * provided on the Intent used when confirming payment. - */ - fun setOnBehalfOf(onBehalfOf: String) { - if (isAttachedToWindow) { - doWithCardWidgetViewModel(viewModelStoreOwner) { viewModel -> - viewModel.onBehalfOf = onBehalfOf - } - } - } - /** * Clear all entered data and hide all error messages. */ From 0b63abb680fdcf71dcd3ea40e285f8a4e912f9fc Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Tue, 14 May 2024 18:53:36 -0400 Subject: [PATCH 08/10] Add OBO tests --- .../android/view/CardWidgetViewModelTest.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt index 5dd60cc85a5..31350e3c9c0 100644 --- a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt +++ b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt @@ -7,6 +7,7 @@ import com.stripe.android.PaymentConfiguration import com.stripe.android.utils.FakeCardElementConfigRepository import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import okhttp3.internal.wait import org.junit.Test private val paymentConfig = PaymentConfiguration( @@ -90,4 +91,45 @@ class CardWidgetViewModelTest { val obo: String? = handle["on_behalf_of"] assertThat(obo).isEqualTo("test") } + + @Test + fun `Setting valid OBO re-fetches correct eligibility`() = runTest(testDispatcher) { + val stripeRepository = FakeCardElementConfigRepository() + + val viewModel = CardWidgetViewModel( + paymentConfigProvider = { paymentConfig }, + stripeRepository = stripeRepository, + dispatcher = testDispatcher, + handle = SavedStateHandle() + ) + + viewModel.isCbcEligible.test { + assertThat(awaitItem()).isFalse() + stripeRepository.enqueueEligible() + viewModel.onBehalfOf = "valid_obo" + assertThat(awaitItem()).isTrue() + } + } + + @Test + fun `Setting invalid OBO re-fetches correct eligibility`() = runTest(testDispatcher) { + val stripeRepository = FakeCardElementConfigRepository() + + val viewModel = CardWidgetViewModel( + paymentConfigProvider = { paymentConfig }, + stripeRepository = stripeRepository, + dispatcher = testDispatcher, + handle = SavedStateHandle() + ) + + stripeRepository.enqueueEligible() + + viewModel.isCbcEligible.test { + viewModel.onBehalfOf = "valid_obo" + assertThat(awaitItem()).isTrue() + stripeRepository.enqueueNotEligible() + viewModel.onBehalfOf = "invalid_obo" + assertThat(awaitItem()).isFalse() + } + } } From 7fe823663aa1733c5742e7274f1720e87f068be3 Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Tue, 14 May 2024 18:57:01 -0400 Subject: [PATCH 09/10] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2600fd6493..5c156fd4bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,11 @@ ## XX.XX.XX - 2023-XX-XX -## 20.41.1 - 2024-04-22 - ### Payments * [ADDED][8344](https://github.com/stripe/stripe-android/pull/8344) Added support for `onBehalfOf` to `CardInputWidget`, `CardMultilineWidget`, and `CardFormView`. This parameter may be required when setting a connected account as the merchant of record for a payment. For more information, see the [Connect docs](https://docs.stripe.com/connect/charges#on_behalf_of). +## 20.41.1 - 2024-04-22 + ### PaymentSheet * [FIXED][8297](https://github.com/stripe/stripe-android/pull/8297) Improve `PaymentSheet` and `CustomerSheet` loading user experience. From ecca68b54de47d464cfc7ae67b06d29fae1e6a6d Mon Sep 17 00:00:00 2001 From: Tyler Clawson Date: Wed, 15 May 2024 11:38:45 -0400 Subject: [PATCH 10/10] Remove unused imports --- .../test/java/com/stripe/android/view/CardWidgetViewModelTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt index 31350e3c9c0..d9b29546888 100644 --- a/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt +++ b/payments-core/src/test/java/com/stripe/android/view/CardWidgetViewModelTest.kt @@ -7,7 +7,6 @@ import com.stripe.android.PaymentConfiguration import com.stripe.android.utils.FakeCardElementConfigRepository import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest -import okhttp3.internal.wait import org.junit.Test private val paymentConfig = PaymentConfiguration(