diff --git a/payments-ui-core/res/values/strings.xml b/payments-ui-core/res/values/strings.xml
index 2dbd7186560..80409f5d7c9 100644
--- a/payments-ui-core/res/values/strings.xml
+++ b/payments-ui-core/res/values/strings.xml
@@ -4,6 +4,8 @@
interest-free payments of with ]]>
Back
+
+ Full name
Account number
@@ -67,6 +69,8 @@
Buy using a UPI ID
AU BECS Direct Debit
+
+ Bacs Direct Debit
Card
diff --git a/payments-ui-core/src/main/assets/lpms.json b/payments-ui-core/src/main/assets/lpms.json
index 98c09df806b..78142a765bb 100644
--- a/payments-ui-core/src/main/assets/lpms.json
+++ b/payments-ui-core/src/main/assets/lpms.json
@@ -755,6 +755,11 @@
}
}
},
+ {
+ "type": "bacs_debit",
+ "async": true,
+ "fields": []
+ },
{
"type": "revolut_pay",
"async": false,
diff --git a/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt b/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt
index cb8e5e6193c..ad652935f97 100644
--- a/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt
+++ b/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt
@@ -227,6 +227,15 @@ internal val AuBecsDebitRequirement = PaymentMethodRequirements(
confirmPMFromCustomer = true
)
+/**
+ * This defines the requirements for usage as a Payment Method.
+ */
+internal val BacsDebitRequirement = PaymentMethodRequirements(
+ piRequirements = setOf(Delayed),
+ siRequirements = null,
+ confirmPMFromCustomer = null
+)
+
internal val ZipRequirement = PaymentMethodRequirements(
piRequirements = emptySet(),
siRequirements = null,
diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/FormUI.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/FormUI.kt
index bd892db1cc8..121d4ce64af 100644
--- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/FormUI.kt
+++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/FormUI.kt
@@ -24,6 +24,8 @@ import com.stripe.android.ui.core.elements.SaveForFutureUseElement
import com.stripe.android.ui.core.elements.SaveForFutureUseElementUI
import com.stripe.android.ui.core.elements.StaticTextElement
import com.stripe.android.ui.core.elements.StaticTextElementUI
+import com.stripe.android.uicore.elements.CheckboxFieldElement
+import com.stripe.android.uicore.elements.CheckboxFieldUI
import com.stripe.android.uicore.elements.FormElement
import com.stripe.android.uicore.elements.IdentifierSpec
import com.stripe.android.uicore.elements.OTPElement
@@ -76,6 +78,10 @@ fun FormUI(
hiddenIdentifiers,
lastTextFieldIdentifier
)
+ is CheckboxFieldElement -> CheckboxFieldUI(
+ controller = element.controller,
+ enabled = enabled
+ )
is StaticTextElement -> StaticTextElementUI(element)
is SaveForFutureUseElement -> SaveForFutureUseElementUI(enabled, element)
is AfterpayClearpayHeaderElement -> AfterpayClearpayElementUI(
diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/ContactInformationSpec.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/ContactInformationSpec.kt
index e746ededed8..c708f7e0ece 100644
--- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/ContactInformationSpec.kt
+++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/ContactInformationSpec.kt
@@ -1,6 +1,7 @@
package com.stripe.android.ui.core.elements
import androidx.annotation.RestrictTo
+import androidx.annotation.StringRes
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import com.stripe.android.ui.core.R
@@ -23,6 +24,9 @@ data class ContactInformationSpec(
val collectEmail: Boolean = true,
@SerialName("collect_phone")
val collectPhone: Boolean = true,
+ @SerialName("name_label")
+ @StringRes
+ val nameLabel: Int = R.string.stripe_name_on_card
) : FormItemSpec() {
override val apiPath: IdentifierSpec = IdentifierSpec()
@@ -31,7 +35,7 @@ data class ContactInformationSpec(
SimpleTextElement(
controller = SimpleTextFieldController(
textFieldConfig = SimpleTextFieldConfig(
- label = R.string.stripe_name_on_card,
+ label = nameLabel,
capitalization = KeyboardCapitalization.Words,
keyboard = KeyboardType.Text
),
diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt
index bddc362884e..2203f954887 100644
--- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt
+++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt
@@ -20,6 +20,7 @@ import com.stripe.android.paymentsheet.forms.AlipayRequirement
import com.stripe.android.paymentsheet.forms.AlmaRequirement
import com.stripe.android.paymentsheet.forms.AmazonPayRequirement
import com.stripe.android.paymentsheet.forms.AuBecsDebitRequirement
+import com.stripe.android.paymentsheet.forms.BacsDebitRequirement
import com.stripe.android.paymentsheet.forms.BancontactRequirement
import com.stripe.android.paymentsheet.forms.BlikRequirement
import com.stripe.android.paymentsheet.forms.BoletoRequirement
@@ -46,7 +47,10 @@ import com.stripe.android.paymentsheet.forms.UpiRequirement
import com.stripe.android.paymentsheet.forms.ZipRequirement
import com.stripe.android.ui.core.BillingDetailsCollectionConfiguration
import com.stripe.android.ui.core.R
+import com.stripe.android.ui.core.elements.AddressSpec
import com.stripe.android.ui.core.elements.AfterpayClearpayHeaderElement.Companion.isClearpay
+import com.stripe.android.ui.core.elements.BacsDebitBankAccountSpec
+import com.stripe.android.ui.core.elements.BacsDebitConfirmSpec
import com.stripe.android.ui.core.elements.CardBillingSpec
import com.stripe.android.ui.core.elements.CardDetailsSectionSpec
import com.stripe.android.ui.core.elements.CashAppPayMandateTextSpec
@@ -468,6 +472,29 @@ class LpmRepository constructor(
requirement = AuBecsDebitRequirement,
formSpec = LayoutSpec(sharedDataSpec.fields)
)
+ PaymentMethod.Type.BacsDebit.code -> {
+ val localFields = listOf(
+ ContactInformationSpec(
+ collectPhone = false,
+ nameLabel = R.string.stripe_bacs_full_name
+ ),
+ BacsDebitBankAccountSpec(),
+ AddressSpec(),
+ BacsDebitConfirmSpec()
+ )
+
+ SupportedPaymentMethod(
+ code = "bacs_debit",
+ requiresMandate = true,
+ displayNameResource = R.string.stripe_paymentsheet_payment_method_bacs_debit,
+ iconResource = R.drawable.stripe_ic_paymentsheet_pm_bank,
+ lightThemeIconUrl = sharedDataSpec.selectorIcon?.lightThemePng,
+ darkThemeIconUrl = sharedDataSpec.selectorIcon?.darkThemePng,
+ tintIconOnSelection = true,
+ requirement = BacsDebitRequirement,
+ formSpec = LayoutSpec(items = sharedDataSpec.fields + localFields)
+ )
+ }
PaymentMethod.Type.USBankAccount.code -> {
val pmo = stripeIntent.getPaymentMethodOptions()[PaymentMethod.Type.USBankAccount.code]
val verificationMethod = (pmo as? Map<*, *>)?.get("verification_method") as? String
diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestBacs.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestBacs.kt
new file mode 100644
index 00000000000..8b2fe28bfa1
--- /dev/null
+++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestBacs.kt
@@ -0,0 +1,46 @@
+package com.stripe.android.lpm
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.stripe.android.BasePlaygroundTest
+import com.stripe.android.paymentsheet.example.playground.settings.AutomaticPaymentMethodsSettingsDefinition
+import com.stripe.android.paymentsheet.example.playground.settings.Country
+import com.stripe.android.paymentsheet.example.playground.settings.CountrySettingsDefinition
+import com.stripe.android.paymentsheet.example.playground.settings.Currency
+import com.stripe.android.paymentsheet.example.playground.settings.CurrencySettingsDefinition
+import com.stripe.android.paymentsheet.example.playground.settings.DelayedPaymentMethodsSettingsDefinition
+import com.stripe.android.test.core.AuthorizeAction
+import com.stripe.android.test.core.TestParameters
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+internal class TestBacs : BasePlaygroundTest() {
+ @Test
+ fun testBacsWhenConfirmed() {
+ testDriver.confirmNewOrGuestComplete(
+ testParameters = createTestParameters(AuthorizeAction.Bacs.Confirm)
+ )
+ }
+
+ @Test
+ fun testBacsWhenCancelled() {
+ testDriver.confirmNewOrGuestComplete(
+ testParameters = createTestParameters(AuthorizeAction.Bacs.ModifyDetails)
+ )
+ }
+
+ private fun createTestParameters(
+ bacsAuthAction: AuthorizeAction.Bacs
+ ): TestParameters {
+ return TestParameters.create(
+ paymentMethodCode = "bacs_debit",
+ ) { settings ->
+ settings[AutomaticPaymentMethodsSettingsDefinition] = true
+ settings[DelayedPaymentMethodsSettingsDefinition] = true
+ settings[CountrySettingsDefinition] = Country.GB
+ settings[CurrencySettingsDefinition] = Currency.GBP
+ }.copy(
+ authorizationAction = bacsAuthAction
+ )
+ }
+}
diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/FieldPopulator.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/FieldPopulator.kt
index 91eb9920030..b26b94aa1df 100644
--- a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/FieldPopulator.kt
+++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/FieldPopulator.kt
@@ -14,6 +14,8 @@ import com.stripe.android.paymentsheet.example.playground.settings.DefaultBillin
import com.stripe.android.test.core.ui.Selectors
import com.stripe.android.ui.core.elements.AddressSpec
import com.stripe.android.ui.core.elements.AuBankAccountNumberSpec
+import com.stripe.android.ui.core.elements.BacsDebitBankAccountSpec
+import com.stripe.android.ui.core.elements.BacsDebitConfirmSpec
import com.stripe.android.ui.core.elements.BoletoTaxIdSpec
import com.stripe.android.ui.core.elements.BsbSpec
import com.stripe.android.ui.core.elements.CardBillingSpec
@@ -99,6 +101,8 @@ internal class FieldPopulator(
val cardCvc: String = "321",
val auBecsBsbNumber: String = "000000",
val auBecsAccountNumber: String = "000123456",
+ val bacsSortCode: String = "108800",
+ val bacsAccountNumber: String = "00012345",
val boletoTaxId: String = "00000000000",
)
@@ -156,6 +160,16 @@ internal class FieldPopulator(
selectors.getAuAccountNumber()
.assertContentDescriptionEquals(values.auBecsAccountNumber)
}
+ is BacsDebitBankAccountSpec -> {
+ selectors.getBacsAccountNumber()
+ .assertContentDescriptionEquals(values.bacsAccountNumber)
+ selectors.getBacsSortCode()
+ .assertContentDescriptionEquals(values.bacsSortCode)
+ }
+ is BacsDebitConfirmSpec -> {
+ selectors.getBacsConfirmed()
+ .assertIsOn()
+ }
is DropdownSpec -> {}
is IbanSpec -> {}
is KlarnaCountrySpec -> {}
@@ -212,6 +226,17 @@ internal class FieldPopulator(
performTextInput(values.auBecsAccountNumber)
}
}
+ is BacsDebitBankAccountSpec -> {
+ selectors.getBacsAccountNumber()
+ .performTextInput(values.bacsAccountNumber)
+ selectors.getBacsSortCode()
+ .performTextInput(values.bacsSortCode)
+ }
+ is BacsDebitConfirmSpec -> {
+ selectors.getBacsConfirmed()
+ .performScrollTo()
+ .performClick()
+ }
is IbanSpec -> {}
is KlarnaCountrySpec -> {}
is CardBillingSpec -> {
diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/PlaygroundTestDriver.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/PlaygroundTestDriver.kt
index c7d2a122ec7..5364b90abf8 100644
--- a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/PlaygroundTestDriver.kt
+++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/PlaygroundTestDriver.kt
@@ -770,7 +770,14 @@ internal class PlaygroundTestDriver(
}.isSuccess
}
}
-
+ is AuthorizeAction.Bacs.Confirm -> {}
+ is AuthorizeAction.Bacs.ModifyDetails -> {
+ buyButton.apply {
+ waitProcessingComplete()
+ isEnabled()
+ isDisplayed()
+ }
+ }
null -> {}
}
} else {
diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt
index 5bacc0ac565..e3a1e5f6adf 100644
--- a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt
+++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt
@@ -129,4 +129,16 @@ internal sealed interface AuthorizeAction {
override fun text(checkoutMode: CheckoutMode): String = ""
override val requiresBrowser: Boolean = true
}
+
+ sealed interface Bacs : AuthorizeAction {
+ object Confirm : Bacs {
+ override fun text(checkoutMode: CheckoutMode): String = "Confirm"
+ override val requiresBrowser: Boolean = false
+ }
+
+ object ModifyDetails : Bacs {
+ override fun text(checkoutMode: CheckoutMode): String = "Modify details"
+ override val requiresBrowser: Boolean = false
+ }
+ }
}
diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/ui/Selectors.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/ui/Selectors.kt
index f87520ac8e3..a5681a4a6a4 100644
--- a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/ui/Selectors.kt
+++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/ui/Selectors.kt
@@ -2,7 +2,10 @@ package com.stripe.android.test.core.ui
import android.content.pm.PackageManager
import androidx.annotation.StringRes
+import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
@@ -27,6 +30,7 @@ import com.stripe.android.ui.core.elements.SAVE_FOR_FUTURE_CHECKBOX_TEST_TAG
import kotlin.time.Duration.Companion.seconds
import com.stripe.android.R as StripeR
import com.stripe.android.core.R as CoreR
+import com.stripe.android.paymentsheet.R as PaymentSheetR
import com.stripe.android.ui.core.R as PaymentsUiCoreR
import com.stripe.android.uicore.R as UiCoreR
@@ -122,6 +126,7 @@ internal class Selectors(
private val checkoutMode =
testParameters.playgroundSettingsSnapshot[CheckoutModeSettingsDefinition]
+ @OptIn(ExperimentalTestApi::class)
val authorizeAction = when (testParameters.authorizationAction) {
is AuthorizeAction.AuthorizePayment -> {
object : UiAutomatorText(
@@ -163,6 +168,50 @@ internal class Selectors(
) {}
}
+ is AuthorizeAction.Bacs.Confirm -> {
+ object : UiAutomatorText(
+ label = testParameters.authorizationAction.text(checkoutMode),
+ className = "android.widget.Button",
+ device = device
+ ) {
+ override fun click() {
+ composeTestRule.waitUntilExactlyOneExists(
+ hasText(
+ getResourceString(
+ PaymentSheetR.string.stripe_paymentsheet_bacs_mandate_title
+ )
+ )
+ )
+
+ composeTestRule.onNodeWithText(
+ getResourceString(PaymentSheetR.string.stripe_paymentsheet_confirm)
+ ).performClick()
+ }
+ }
+ }
+
+ is AuthorizeAction.Bacs.ModifyDetails -> {
+ object : UiAutomatorText(
+ label = testParameters.authorizationAction.text(checkoutMode),
+ className = "android.widget.Button",
+ device = device
+ ) {
+ override fun click() {
+ composeTestRule.waitUntilExactlyOneExists(
+ hasText(getResourceString(PaymentSheetR.string.stripe_paymentsheet_bacs_mandate_title))
+ )
+
+ composeTestRule.onNodeWithText(
+ getResourceString(
+ PaymentSheetR
+ .string
+ .stripe_paymentsheet_bacs_modify_details_button_label
+ )
+ ).performClick()
+ }
+ }
+ }
+
else -> null
}
@@ -213,6 +262,18 @@ internal class Selectors(
getResourceString(StripeR.string.stripe_becs_widget_account_number)
)
+ fun getBacsSortCode() = composeTestRule.onNodeWithText(
+ getResourceString(PaymentsUiCoreR.string.stripe_bacs_sort_code)
+ )
+
+ fun getBacsAccountNumber() = composeTestRule.onNodeWithText(
+ getResourceString(StripeR.string.stripe_becs_widget_account_number)
+ )
+
+ fun getBacsConfirmed() = composeTestRule.onNode(
+ isToggleable().and(hasTestTag("BACS_MANDATE_CHECKBOX"))
+ )
+
fun getBoletoTaxId() = composeTestRule.onNodeWithText(
getResourceString(PaymentsUiCoreR.string.stripe_boleto_tax_id_label)
)
diff --git a/paymentsheet/src/test/resources/bacs_debit-support.csv b/paymentsheet/src/test/resources/bacs_debit-support.csv
new file mode 100644
index 00000000000..24b2e01fb76
--- /dev/null
+++ b/paymentsheet/src/test/resources/bacs_debit-support.csv
@@ -0,0 +1,49 @@
+lpm, hasCustomer, allowsDelayedPayment, intentSetupFutureUsage, intentHasShipping, intentLpms, supportCustomerSavedCard, formExists, formType, supportsAdding
+bacs_debit, true, true, off_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, true, true, off_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, false, off_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, true, false, off_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, true, on_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, true, true, on_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, false, on_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, true, false, on_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, true, null, false, card/bacs_debit, false, true, oneTime, true
+bacs_debit, true, true, null, false, card/eps/bacs_debit, false, true, oneTime, true
+bacs_debit, true, false, null, false, card/bacs_debit, false, false, not available, false
+bacs_debit, true, false, null, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, true, off_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, false, true, off_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, false, off_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, false, false, off_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, true, on_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, false, true, on_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, false, on_session, false, card/bacs_debit, false, false, not available, false
+bacs_debit, false, false, on_session, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, true, null, false, card/bacs_debit, false, true, oneTime, true
+bacs_debit, false, true, null, false, card/eps/bacs_debit, false, true, oneTime, true
+bacs_debit, false, false, null, false, card/bacs_debit, false, false, not available, false
+bacs_debit, false, false, null, false, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, true, off_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, true, true, off_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, false, off_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, true, false, off_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, true, on_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, true, true, on_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, false, on_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, true, false, on_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, true, true, null, true, card/bacs_debit, false, true, oneTime, true
+bacs_debit, true, true, null, true, card/eps/bacs_debit, false, true, oneTime, true
+bacs_debit, true, false, null, true, card/bacs_debit, false, false, not available, false
+bacs_debit, true, false, null, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, true, off_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, false, true, off_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, false, off_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, false, false, off_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, true, on_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, false, true, on_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, false, on_session, true, card/bacs_debit, false, false, not available, false
+bacs_debit, false, false, on_session, true, card/eps/bacs_debit, false, false, not available, false
+bacs_debit, false, true, null, true, card/bacs_debit, false, true, oneTime, true
+bacs_debit, false, true, null, true, card/eps/bacs_debit, false, true, oneTime, true
+bacs_debit, false, false, null, true, card/bacs_debit, false, false, not available, false
+bacs_debit, false, false, null, true, card/eps/bacs_debit, false, false, not available, false
\ No newline at end of file