Skip to content

Commit

Permalink
callback for postal code complete (#4424)
Browse files Browse the repository at this point in the history
* callback for us zip code complete

* resolve comments

* have valid callback for all non-empty global postals

* lint and dump
  • Loading branch information
ccen-stripe authored Dec 9, 2021
1 parent 0ac986d commit c75cef9
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 1 deletion.
1 change: 1 addition & 0 deletions payments-core/api/payments-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -5671,6 +5671,7 @@ public abstract interface class com/stripe/android/view/CardInputListener {
public abstract fun onCvcComplete ()V
public abstract fun onExpirationComplete ()V
public abstract fun onFocusChange (Lcom/stripe/android/view/CardInputListener$FocusField;)V
public abstract fun onPostalCodeComplete ()V
}

public final class com/stripe/android/view/CardInputListener$FocusField : java/lang/Enum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,11 @@ interface CardInputListener {
* the user edits the CVC.
*/
fun onCvcComplete()

/**
* Called when a valid postal code has been entered.
* May be called multiple times, if the user edits the field.
* If the [CardWidget] is not collecting US card, any non-empty postal is considered valid.
*/
fun onPostalCodeComplete()
}
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,12 @@ class CardInputWidget @JvmOverloads constructor(
}
}

postalCodeEditText.setAfterTextChangedListener {
if (isPostalRequired() && postalCodeEditText.hasValidPostal()) {
cardInputListener?.onPostalCodeComplete()
}
}

cardNumberEditText.completionCallback = {
scrollEnd()
cardInputListener?.onCardComplete()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ class CardMultilineWidget @JvmOverloads constructor(
}
}

private fun isPostalRequired() =
(postalCodeRequired || usZipCodeRequired) && shouldShowPostalCode

/**
* A [PaymentMethodCreateParams.Card] representing the card details if all fields are valid;
* otherwise `null`
Expand Down Expand Up @@ -367,6 +370,12 @@ class CardMultilineWidget @JvmOverloads constructor(
cvcEditText.shouldShowError = false
}

postalCodeEditText.setAfterTextChangedListener {
if (isPostalRequired() && postalCodeEditText.hasValidPostal()) {
cardInputListener?.onPostalCodeComplete()
}
}

adjustViewForPostalCodeAttribute(shouldShowPostalCode)

cardNumberEditText.updateLengthFilter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ class PostalCodeEditText @JvmOverloads constructor(
US
}

/**
* Returns if the postal is valid. If config is not US, any non-empty postal is valid.
*/
internal fun hasValidPostal() =
config == Config.US && ZIP_CODE_PATTERN.matcher(fieldText)
.matches() || config == Config.Global && fieldText.isNotEmpty()

private companion object {
private const val MAX_LENGTH_US = 5

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,48 @@ internal class CardInputWidgetTest {
)
}

@Test
fun usZipCodeRequired_whenFalse_shouldNotCallOnPostalCodeComplete() {
cardInputWidget.usZipCodeRequired = false
postalCodeEditText.setText(POSTAL_CODE_VALUE)
assertThat(cardInputListener.onPostalCodeCompleteCalls).isEqualTo(0)
}

@Test
fun usZipCodeRequired_whenTrue_withValidZip_shouldCallOnPostalCodeComplete() {
cardInputWidget.usZipCodeRequired = true
postalCodeEditText.setText(POSTAL_CODE_VALUE)
assertThat(cardInputListener.onPostalCodeCompleteCalls).isEqualTo(1)
}

@Test
fun postalCodeEnabled_whenFalse_shouldNotCallOnPostalCodeComplete() {
cardInputWidget.postalCodeEnabled = false
postalCodeEditText.setText("123")
assertThat(cardInputListener.onPostalCodeCompleteCalls).isEqualTo(0)
}

@Test
fun usZipCodeRequired_whenTrue_withInvalidZip_shouldNotCallOnPostalCodeComplete() {
cardInputWidget.usZipCodeRequired = true
postalCodeEditText.setText("1234")
assertThat(cardInputListener.onPostalCodeCompleteCalls).isEqualTo(0)
}

@Test
fun postalCode_whenTrue_withNonEmptyZip_shouldCallOnPostalCodeComplete() {
cardInputWidget.postalCodeRequired = true
postalCodeEditText.setText("1234")
assertThat(cardInputListener.onPostalCodeCompleteCalls).isEqualTo(1)
}

@Test
fun postalCode_whenTrue_withEmptyZip_shouldNotCallOnPostalCodeComplete() {
cardInputWidget.postalCodeRequired = true
postalCodeEditText.setText("")
assertThat(cardInputListener.onPostalCodeCompleteCalls).isEqualTo(0)
}

@Test
fun `setCvcLabel is not reset when card number entered`() {
cardInputWidget.setCvcLabel("123")
Expand Down Expand Up @@ -1578,6 +1620,7 @@ internal class CardInputWidgetTest {
var cardCompleteCalls = 0
var expirationCompleteCalls = 0
var cvcCompleteCalls = 0
var onPostalCodeCompleteCalls = 0

override fun onFocusChange(focusField: CardInputListener.FocusField) {
focusedFields.add(focusField)
Expand All @@ -1594,6 +1637,10 @@ internal class CardInputWidgetTest {
override fun onCvcComplete() {
cvcCompleteCalls++
}

override fun onPostalCodeComplete() {
onPostalCodeCompleteCalls++
}
}

private companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,35 +330,42 @@ internal class CardMultilineWidgetTest {
@Test
fun paymentMethodCreateParams_whenPostalCodeIsRequiredAndValueIsBlank_returnsNull() {
cardMultilineWidget.setShouldShowPostalCode(true)
cardMultilineWidget.setCardInputListener(fullCardListener)
cardMultilineWidget.postalCodeRequired = true

fullGroup.cardNumberEditText.setText(VISA_WITH_SPACES)
fullGroup.expiryDateEditText.append("12")
fullGroup.expiryDateEditText.append("50")
fullGroup.cvcEditText.append(CVC_VALUE_COMMON)
fullGroup.postalCodeEditText.setText("")

assertThat(cardMultilineWidget.paymentMethodCreateParams)
.isNull()
verify(fullCardListener, never()).onPostalCodeComplete()
}

@Test
fun paymentMethodCreateParams_whenPostalCodeIsRequiredAndValueIsNotBlank_returnsNotNull() {
cardMultilineWidget.setShouldShowPostalCode(true)
cardMultilineWidget.postalCodeRequired = false
cardMultilineWidget.setCardInputListener(fullCardListener)
cardMultilineWidget.postalCodeRequired = true

fullGroup.cardNumberEditText.setText(VISA_WITH_SPACES)
fullGroup.expiryDateEditText.append("12")
fullGroup.expiryDateEditText.append("50")
fullGroup.cvcEditText.append(CVC_VALUE_COMMON)
fullGroup.postalCodeEditText.setText("1234")

assertThat(cardMultilineWidget.paymentMethodCreateParams)
.isNotNull()
verify(fullCardListener).onPostalCodeComplete()
}

@Test
fun paymentMethodCreateParams_whenPostalCodeIsNotRequiredAndValueIsBlank_returnsNotNull() {
cardMultilineWidget.setShouldShowPostalCode(true)
cardMultilineWidget.postalCodeRequired = false
cardMultilineWidget.setCardInputListener(fullCardListener)

fullGroup.cardNumberEditText.setText(VISA_WITH_SPACES)
fullGroup.expiryDateEditText.append("12")
Expand All @@ -367,6 +374,7 @@ internal class CardMultilineWidgetTest {

assertThat(cardMultilineWidget.paymentMethodCreateParams)
.isNotNull()
verify(fullCardListener, never()).onPostalCodeComplete()
}

@Test
Expand Down Expand Up @@ -586,6 +594,9 @@ internal class CardMultilineWidgetTest {
verify(noZipCardListener).onFocusChange(CardInputListener.FocusField.ExpiryDate)
assertThat(noZipGroup.expiryDateEditText.hasFocus())
.isTrue()

verify(fullCardListener, never()).onPostalCodeComplete()
verify(noZipCardListener, never()).onPostalCodeComplete()
}

@Test
Expand All @@ -606,6 +617,9 @@ internal class CardMultilineWidgetTest {
verify(noZipCardListener).onFocusChange(CardInputListener.FocusField.Cvc)
assertThat(noZipGroup.cvcEditText.hasFocus())
.isTrue()

verify(fullCardListener, never()).onPostalCodeComplete()
verify(noZipCardListener, never()).onPostalCodeComplete()
}

@Test
Expand All @@ -630,6 +644,9 @@ internal class CardMultilineWidgetTest {
verify(noZipCardListener, never()).onFocusChange(CardInputListener.FocusField.PostalCode)
assertThat(noZipGroup.cvcEditText.hasFocus())
.isTrue()

verify(fullCardListener, never()).onPostalCodeComplete()
verify(noZipCardListener, never()).onPostalCodeComplete()
}

@Test
Expand All @@ -646,6 +663,10 @@ internal class CardMultilineWidgetTest {
.isTrue()
assertThat(fullGroup.cardNumberEditText.text?.toString())
.isEqualTo(VISA_WITH_SPACES.take(VISA_WITH_SPACES.length - 1))

verify(fullCardListener, never()).onPostalCodeComplete()

verify(fullCardListener, never()).onPostalCodeComplete()
}

@Test
Expand All @@ -662,6 +683,8 @@ internal class CardMultilineWidgetTest {
.isTrue()
assertThat(noZipGroup.cardNumberEditText.text?.toString())
.isEqualTo(VISA_WITH_SPACES.take(VISA_WITH_SPACES.length - 1))

verify(noZipCardListener, never()).onPostalCodeComplete()
}

@Test
Expand Down Expand Up @@ -694,6 +717,9 @@ internal class CardMultilineWidgetTest {
.isTrue()
assertThat(noZipGroup.expiryDateEditText.fieldText)
.isEqualTo("12/5")

verify(fullCardListener, never()).onPostalCodeComplete()
verify(noZipCardListener, never()).onPostalCodeComplete()
}

@Test
Expand Down Expand Up @@ -805,6 +831,7 @@ internal class CardMultilineWidgetTest {
@Test
fun usZipCodeRequired_whenFalse_shouldSetPostalCodeHint() {
cardMultilineWidget.usZipCodeRequired = false
cardMultilineWidget.setCardInputListener(fullCardListener)
assertThat(cardMultilineWidget.postalInputLayout.hint)
.isEqualTo("Postal code")

Expand All @@ -826,11 +853,14 @@ internal class CardMultilineWidgetTest {
.build()
)
)

verify(fullCardListener, never()).onPostalCodeComplete()
}

@Test
fun usZipCodeRequired_whenTrue_withInvalidZipCode_shouldReturnNullCard() {
cardMultilineWidget.usZipCodeRequired = true
cardMultilineWidget.setCardInputListener(fullCardListener)
assertThat(cardMultilineWidget.postalInputLayout.hint)
.isEqualTo("ZIP Code")

Expand All @@ -843,11 +873,14 @@ internal class CardMultilineWidgetTest {
fullGroup.postalCodeEditText.setText("1234")
assertThat(cardMultilineWidget.cardParams)
.isNull()

verify(fullCardListener, never()).onPostalCodeComplete()
}

@Test
fun usZipCodeRequired_whenTrue_withValidZipCode_shouldReturnNotNullCard() {
cardMultilineWidget.usZipCodeRequired = true
cardMultilineWidget.setCardInputListener(fullCardListener)
assertThat(cardMultilineWidget.postalInputLayout.hint)
.isEqualTo("ZIP Code")

Expand All @@ -872,6 +905,8 @@ internal class CardMultilineWidgetTest {
.build()
)
)

verify(fullCardListener).onPostalCodeComplete()
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ internal class CardDataCollectionFragment<ViewModelType : BaseSheetViewModel<*>>
// move to first field when CVC is complete
billingAddressView.focusFirstField()
}

override fun onPostalCodeComplete() {}
})

sheetViewModel.processing.observe(viewLifecycleOwner) { isProcessing ->
Expand Down

0 comments on commit c75cef9

Please sign in to comment.