Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pasting a 19 digit PAN in CardNumberEditText #2821

Merged
merged 1 commit into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,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 @@ -186,13 +186,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 @@ -233,8 +235,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 @@ -250,13 +255,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 @@ -311,6 +329,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