Skip to content

Commit a96dae5

Browse files
Fix mandates in vertical mode. (#9196)
1 parent 315487c commit a96dae5

File tree

8 files changed

+227
-42
lines changed

8 files changed

+227
-42
lines changed

payments-core-testing/detekt-baseline.xml

+1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
<ID>EmptyFunctionBlock:AbsPaymentController.kt$AbsPaymentController${ }</ID>
66
<ID>TooManyFunctions:AbsFakeStripeRepository.kt$AbsFakeStripeRepository : StripeRepository</ID>
77
<ID>TooManyFunctions:AbsPaymentController.kt$AbsPaymentController : PaymentController</ID>
8+
<ID>TooManyFunctions:PaymentMethodFactory.kt$PaymentMethodFactory</ID>
89
</CurrentIssues>
910
</SmellBaseline>

payments-core-testing/src/main/java/com/stripe/android/testing/PaymentMethodFactory.kt

+55-7
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ object PaymentMethodFactory {
8787
liveMode = false,
8888
type = PaymentMethod.Type.USBankAccount,
8989
code = PaymentMethod.Type.USBankAccount.code,
90+
usBankAccount = PaymentMethod.USBankAccount(
91+
accountHolderType = PaymentMethod.USBankAccount.USBankAccountHolderType.INDIVIDUAL,
92+
accountType = PaymentMethod.USBankAccount.USBankAccountType.CHECKING,
93+
bankName = "STRIPE TEST BANK",
94+
fingerprint = "FFDMA0xfhBjWSZLu",
95+
last4 = "6789",
96+
financialConnectionsAccount = "fca_1PvmkYLu5o3P18Zp3o1YDi1z",
97+
networks = PaymentMethod.USBankAccount.USBankNetworks(
98+
preferred = "ach",
99+
supported = listOf("ach")
100+
),
101+
routingNumber = "110000000",
102+
)
90103
)
91104
}
92105

@@ -101,13 +114,7 @@ object PaymentMethodFactory {
101114
}
102115

103116
fun convertCardToJson(paymentMethod: PaymentMethod): JSONObject {
104-
val paymentMethodJson = JSONObject()
105-
106-
paymentMethodJson.put("id", paymentMethod.id)
107-
paymentMethodJson.put("type", paymentMethod.type?.code)
108-
paymentMethodJson.put("created", paymentMethod.created)
109-
paymentMethodJson.put("customer", paymentMethod.customerId)
110-
paymentMethodJson.put("livemode", paymentMethod.liveMode)
117+
val paymentMethodJson = convertGenericPaymentMethodToJson(paymentMethod)
111118

112119
val card = paymentMethod.card
113120
val cardJson = JSONObject()
@@ -133,4 +140,45 @@ object PaymentMethodFactory {
133140

134141
return paymentMethodJson
135142
}
143+
144+
fun convertUsBankAccountToJson(paymentMethod: PaymentMethod): JSONObject {
145+
val paymentMethodJson = convertGenericPaymentMethodToJson(paymentMethod)
146+
147+
val usBankAccount = paymentMethod.usBankAccount!!
148+
val usBankAccountJson = JSONObject()
149+
150+
usBankAccountJson.put("account_holder_type", usBankAccount.accountHolderType.value)
151+
usBankAccountJson.put("account_type", usBankAccount.accountType.value)
152+
usBankAccountJson.put("bank_name", usBankAccount.bankName)
153+
usBankAccountJson.put("financial_connections_account", usBankAccount.financialConnectionsAccount)
154+
usBankAccountJson.put("fingerprint", usBankAccount.fingerprint)
155+
usBankAccountJson.put("last4", usBankAccount.last4)
156+
usBankAccountJson.put("linked_account", usBankAccount.linkedAccount)
157+
usBankAccountJson.put("routing_number", usBankAccount.routingNumber)
158+
159+
val networksJson = JSONObject()
160+
networksJson.put("preferred", usBankAccount.networks?.preferred)
161+
val supportedJson = JSONArray()
162+
usBankAccount.networks?.supported?.forEach { supported ->
163+
supportedJson.put(supported)
164+
}
165+
networksJson.put("supported", supportedJson)
166+
usBankAccountJson.put("networks", networksJson)
167+
168+
paymentMethodJson.put("us_bank_account", usBankAccountJson)
169+
170+
return paymentMethodJson
171+
}
172+
173+
private fun convertGenericPaymentMethodToJson(paymentMethod: PaymentMethod): JSONObject {
174+
val paymentMethodJson = JSONObject()
175+
176+
paymentMethodJson.put("id", paymentMethod.id)
177+
paymentMethodJson.put("type", paymentMethod.type?.code)
178+
paymentMethodJson.put("created", paymentMethod.created)
179+
paymentMethodJson.put("customer", paymentMethod.customerId)
180+
paymentMethodJson.put("livemode", paymentMethod.liveMode)
181+
182+
return paymentMethodJson
183+
}
136184
}

paymentsheet/detekt-baseline.xml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<ID>LongMethod:FormViewModelTest.kt$FormViewModelTest$@Test fun `Verify params are set when required address fields are complete`()</ID>
3333
<ID>LongMethod:PaymentOptionFactory.kt$PaymentOptionFactory$fun create(selection: PaymentSelection): PaymentOption</ID>
3434
<ID>LongMethod:PaymentSheetConfigurationKtx.kt$internal fun PaymentSheet.Appearance.parseAppearance()</ID>
35+
<ID>LongMethod:PaymentSheetScreen.kt$@Composable private fun PaymentSheetContent( viewModel: BaseSheetViewModel, headerText: ResolvableString?, walletsState: WalletsState?, walletsProcessingState: WalletsProcessingState?, error: ResolvableString?, currentScreen: PaymentSheetScreen, mandateText: MandateText?, modifier: Modifier, )</ID>
3536
<ID>LongMethod:PaymentSheetViewModelTest.kt$PaymentSheetViewModelTest$@Test fun `Can complete payment after switching to another LPM from card selection with inline Link signup state`()</ID>
3637
<ID>LongMethod:PlaceholderHelperTest.kt$PlaceholderHelperTest$@Test fun `Test correct placeholder is removed for placeholder spec`()</ID>
3738
<ID>LongMethod:USBankAccountEmitters.kt$@Composable internal fun USBankAccountEmitters( viewModel: USBankAccountFormViewModel, usBankAccountFormArgs: USBankAccountFormArguments, )</ID>

paymentsheet/src/main/java/com/stripe/android/paymentsheet/navigation/PaymentSheetScreen.kt

+11
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ internal sealed interface PaymentSheetScreen {
7575
val walletsDividerSpacing: Dp
7676
val animationStyle: AnimationStyle
7777
get() = AnimationStyle.FullPage
78+
val showsMandates: Boolean
7879

7980
fun topBarState(): StateFlow<PaymentSheetTopBarState?>
8081

@@ -94,6 +95,7 @@ internal sealed interface PaymentSheetScreen {
9495
override val topContentPadding: Dp = 0.dp
9596
override val bottomContentPadding: Dp = 0.dp
9697
override val walletsDividerSpacing: Dp = horizontalModeWalletsDividerSpacing
98+
override val showsMandates: Boolean = false
9799

98100
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
99101
return stateFlowOf(null)
@@ -131,6 +133,7 @@ internal sealed interface PaymentSheetScreen {
131133
override val bottomContentPadding: Dp = 0.dp
132134
override val walletsDividerSpacing: Dp = horizontalModeWalletsDividerSpacing
133135
override val animationStyle: AnimationStyle = AnimationStyle.PrimaryButtonAnchored
136+
override val showsMandates: Boolean = true
134137

135138
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
136139
return interactor.state.mapAsStateFlow { state ->
@@ -188,6 +191,7 @@ internal sealed interface PaymentSheetScreen {
188191
override val bottomContentPadding: Dp = horizontalModeBottomContentPadding
189192
override val walletsDividerSpacing: Dp = horizontalModeWalletsDividerSpacing
190193
override val animationStyle: AnimationStyle = AnimationStyle.PrimaryButtonAnchored
194+
override val showsMandates: Boolean = true
191195

192196
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
193197
return stateFlowOf(
@@ -238,6 +242,7 @@ internal sealed interface PaymentSheetScreen {
238242
override val topContentPadding: Dp = 0.dp
239243
override val bottomContentPadding: Dp = horizontalModeBottomContentPadding
240244
override val walletsDividerSpacing: Dp = horizontalModeWalletsDividerSpacing
245+
override val showsMandates: Boolean = true
241246

242247
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
243248
return stateFlowOf(
@@ -290,6 +295,7 @@ internal sealed interface PaymentSheetScreen {
290295
override val topContentPadding: Dp = 0.dp
291296
override val bottomContentPadding: Dp = 0.dp
292297
override val walletsDividerSpacing: Dp = horizontalModeWalletsDividerSpacing
298+
override val showsMandates: Boolean = false
293299

294300
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
295301
return stateFlowOf(
@@ -328,6 +334,7 @@ internal sealed interface PaymentSheetScreen {
328334
override val topContentPadding: Dp = 0.dp
329335
override val bottomContentPadding: Dp = verticalModeBottomContentPadding
330336
override val walletsDividerSpacing: Dp = verticalModeWalletsDividerSpacing
337+
override val showsMandates: Boolean = true
331338

332339
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
333340
return stateFlowOf(
@@ -373,6 +380,7 @@ internal sealed interface PaymentSheetScreen {
373380
override val topContentPadding: Dp = 0.dp
374381
override val bottomContentPadding: Dp = verticalModeBottomContentPadding
375382
override val walletsDividerSpacing: Dp = verticalModeWalletsDividerSpacing
383+
override val showsMandates: Boolean = true
376384

377385
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
378386
return stateFlowOf(
@@ -410,6 +418,7 @@ internal sealed interface PaymentSheetScreen {
410418
override val topContentPadding: Dp = 0.dp
411419
override val bottomContentPadding: Dp = 0.dp
412420
override val walletsDividerSpacing: Dp = verticalModeWalletsDividerSpacing
421+
override val showsMandates: Boolean = false
413422

414423
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
415424
return interactor.state.mapAsStateFlow { state ->
@@ -461,6 +470,7 @@ internal sealed interface PaymentSheetScreen {
461470
override val topContentPadding: Dp = 0.dp
462471
override val bottomContentPadding: Dp = 0.dp
463472
override val walletsDividerSpacing: Dp = verticalModeWalletsDividerSpacing
473+
override val showsMandates: Boolean = false
464474

465475
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
466476
return stateFlowOf(
@@ -499,6 +509,7 @@ internal sealed interface PaymentSheetScreen {
499509
override val topContentPadding: Dp = 0.dp
500510
override val bottomContentPadding: Dp = verticalModeBottomContentPadding
501511
override val walletsDividerSpacing: Dp = verticalModeWalletsDividerSpacing
512+
override val showsMandates: Boolean = false
502513

503514
override fun topBarState(): StateFlow<PaymentSheetTopBarState?> {
504515
return interactor.cvcCompletionState.mapAsStateFlow { complete ->

paymentsheet/src/main/java/com/stripe/android/paymentsheet/ui/PaymentSheetScreen.kt

+7-4
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,13 @@ private fun PaymentSheetContent(
318318
}
319319
}
320320

321-
if (mandateText?.showAbovePrimaryButton == true) {
321+
if (mandateText?.showAbovePrimaryButton == true && currentScreen.showsMandates) {
322322
Mandate(
323323
mandateText = mandateText.text?.resolve(),
324324
modifier = Modifier
325325
.padding(horizontal = horizontalPadding)
326-
.padding(bottom = 8.dp),
326+
.padding(bottom = 8.dp)
327+
.testTag(PAYMENT_SHEET_MANDATE_TEXT_TEST_TAG),
327328
)
328329
}
329330

@@ -342,12 +343,13 @@ private fun PaymentSheetContent(
342343
PrimaryButton(viewModel)
343344

344345
Box(modifier = modifier) {
345-
if (mandateText?.showAbovePrimaryButton == false) {
346+
if (mandateText?.showAbovePrimaryButton == false && currentScreen.showsMandates) {
346347
Mandate(
347348
mandateText = mandateText.text?.resolve(),
348349
modifier = Modifier
349350
.padding(top = 8.dp)
350-
.padding(horizontal = horizontalPadding),
351+
.padding(horizontal = horizontalPadding)
352+
.testTag(PAYMENT_SHEET_MANDATE_TEXT_TEST_TAG),
351353
)
352354
}
353355
}
@@ -488,4 +490,5 @@ internal fun PaymentSheetViewState.convert(): PrimaryButton.State {
488490

489491
const val PAYMENT_SHEET_PRIMARY_BUTTON_TEST_TAG = "PRIMARY_BUTTON"
490492
const val PAYMENT_SHEET_ERROR_TEXT_TEST_TAG = "PAYMENT_SHEET_ERROR"
493+
internal const val PAYMENT_SHEET_MANDATE_TEXT_TEST_TAG = "PAYMENT_SHEET_MANDATE_TEXT_TEST_TAG"
491494
private const val POST_SUCCESS_ANIMATION_DELAY = 1500L

paymentsheet/src/main/java/com/stripe/android/paymentsheet/viewmodels/BaseSheetViewModel.kt

-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ internal abstract class BaseSheetViewModel(
125125
// Drop the first item, since we don't need to clear errors/mandates when there aren't any.
126126
navigationHandler.currentScreen.drop(1).collect {
127127
clearErrorMessages()
128-
mandateHandler.updateMandateText(mandateText = null, showAbove = false)
129128
}
130129
}
131130
}

paymentsheet/src/test/java/com/stripe/android/paymentsheet/VerticalModePage.kt

+13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.ui.test.onNodeWithTag
99
import androidx.compose.ui.test.performClick
1010
import androidx.compose.ui.test.performScrollTo
1111
import com.stripe.android.model.PaymentMethodCode
12+
import com.stripe.android.paymentsheet.ui.PAYMENT_SHEET_MANDATE_TEXT_TEST_TAG
1213
import com.stripe.android.paymentsheet.ui.PAYMENT_SHEET_PRIMARY_BUTTON_TEST_TAG
1314
import com.stripe.android.paymentsheet.ui.TEST_TAG_ICON_FROM_RES
1415
import com.stripe.android.paymentsheet.verticalmode.TEST_TAG_EDIT_SAVED_CARD
@@ -48,6 +49,18 @@ internal class VerticalModePage(
4849
.assertExists()
4950
}
5051

52+
fun assertMandateExists() {
53+
composeTestRule
54+
.onNode(hasTestTag(PAYMENT_SHEET_MANDATE_TEXT_TEST_TAG))
55+
.assertExists()
56+
}
57+
58+
fun assertMandateDoesNotExists() {
59+
composeTestRule
60+
.onNode(hasTestTag(PAYMENT_SHEET_MANDATE_TEXT_TEST_TAG))
61+
.assertDoesNotExist()
62+
}
63+
5164
fun assertHasSavedPaymentMethods() {
5265
composeTestRule.onNodeWithTag(TEST_TAG_SAVED_TEXT).assertExists()
5366
}

0 commit comments

Comments
 (0)