Skip to content

Commit

Permalink
Support pasting a 19 digit PAN in CardNumberEditText (#2821)
Browse files Browse the repository at this point in the history
  • Loading branch information
mshafrir-stripe authored Sep 8, 2020
1 parent 99bb797 commit 8bdf96f
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ internal sealed class CardNumber {
internal fun getSpacePositions(panLength: Int) = SPACE_POSITIONS[panLength]
?: DEFAULT_SPACE_POSITIONS

private const val MIN_PAN_LENGTH = 14
internal const val MIN_PAN_LENGTH = 14
internal const val MAX_PAN_LENGTH = 19
internal const val DEFAULT_PAN_LENGTH = 16
private val DEFAULT_SPACE_POSITIONS = setOf(4, 9, 14)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ class CardInputWidget @JvmOverloads constructor(
panLength: Int
): String {
val formattedNumber = CardNumber.Unvalidated(
List(panLength) { "0" }.joinToString(separator = "")
"0".repeat(panLength)
).getFormatted(panLength)

return formattedNumber.take(
Expand Down Expand Up @@ -1109,7 +1109,7 @@ class CardInputWidget @JvmOverloads constructor(
14 -> 2
else -> 4
}.let { peekSize ->
List(peekSize) { "0" }.joinToString(separator = "")
"0".repeat(peekSize)
}
}

Expand Down
42 changes: 32 additions & 10 deletions stripe/src/main/java/com/stripe/android/view/CardNumberEditText.kt
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ class CardNumberEditText internal constructor(
}

@JvmSynthetic
internal fun updateLengthFilter() {
filters = arrayOf<InputFilter>(InputFilter.LengthFilter(formattedPanLength))
internal fun updateLengthFilter(maxLength: Int = formattedPanLength) {
filters = arrayOf<InputFilter>(InputFilter.LengthFilter(maxLength))
}

/**
Expand All @@ -185,13 +185,15 @@ class CardNumberEditText internal constructor(
* @param editActionStart the position in the string at which the edit action starts
* @param editActionAddition the number of new characters going into the string (zero for
* delete)
* @param panLength the maximum normalized length of the PAN
* @return an index within the string at which to put the cursor
*/
@JvmSynthetic
internal fun updateSelectionIndex(
newLength: Int,
editActionStart: Int,
editActionAddition: Int
editActionAddition: Int,
panLength: Int = this.panLength
): Int {
var gapsJumped = 0
val gapSet = CardNumber.getSpacePositions(panLength)
Expand Down Expand Up @@ -232,8 +234,11 @@ class CardNumberEditText internal constructor(

private var beforeCardNumber = unvalidatedCardNumber

private var isPastedPan = false

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
if (!ignoreChanges) {
isPastedPan = false
beforeCardNumber = unvalidatedCardNumber

latestChangeStart = start
Expand All @@ -249,13 +254,26 @@ class CardNumberEditText internal constructor(
val cardNumber = CardNumber.Unvalidated(s?.toString().orEmpty())
updateAccountRange(cardNumber)

val formattedNumber = cardNumber.getFormatted(panLength)
this.newCursorPosition = updateSelectionIndex(
formattedNumber.length,
latestChangeStart,
latestInsertionSize
)
this.formattedNumber = formattedNumber
isPastedPan = isPastedPan(start, cardNumber)

if (isPastedPan) {
updateLengthFilter(cardNumber.getFormatted(cardNumber.length).length)
}

if (isPastedPan) {
cardNumber.length
} else {
panLength
}.let { maxPanLength ->
val formattedNumber = cardNumber.getFormatted(maxPanLength)
newCursorPosition = updateSelectionIndex(
formattedNumber.length,
latestChangeStart,
latestInsertionSize,
maxPanLength
)
this.formattedNumber = formattedNumber
}
}

override fun afterTextChanged(s: Editable?) {
Expand Down Expand Up @@ -310,6 +328,10 @@ class CardNumberEditText internal constructor(
unvalidatedCardNumber.isMaxLength ||
(isValid && accountRange != null)
)

private fun isPastedPan(start: Int, cardNumber: CardNumber.Unvalidated): Boolean {
return start == 0 && cardNumber.normalized.length >= CardNumber.MIN_PAN_LENGTH
}
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,26 @@ internal class CardNumberEditTextTest {
.isEqualTo(1)
}

@Test
fun `when 19 digit PAN is pasted, full PAN is accepted and formatted`() {
val cardNumberEditText = CardNumberEditText(
context,
workDispatcher = testDispatcher,
cardAccountRangeRepository = NullCardAccountRangeRepository(),
staticCardAccountRanges = object : StaticCardAccountRanges {
override fun match(
cardNumber: CardNumber.Unvalidated
): AccountRange? = null
}
)

cardNumberEditText.setText("6216828050000000000")
idleLooper()

assertThat(cardNumberEditText.fieldText)
.isEqualTo("6216 8280 5000 0000 000")
}

@Test
fun `updating text with null account range should format text correctly but not set card brand`() {
val cardNumberEditText = CardNumberEditText(
Expand Down

0 comments on commit 8bdf96f

Please sign in to comment.