Skip to content

Commit

Permalink
Fix reliable selected card logic
Browse files Browse the repository at this point in the history
COAND-795
  • Loading branch information
ozgur00 committed Nov 3, 2023
1 parent a1bd7d6 commit 4bf509f
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,6 @@ class DefaultCardDelegate(
detectedCardTypes = filteredDetectedCardTypes
)

val reliableSelectedCard = if (isReliable) selectedOrFirstCardType else null

// perform a Luhn Check if no brands are detected
val enableLuhnCheck = selectedOrFirstCardType?.enableLuhnCheck ?: true

Expand All @@ -328,23 +326,23 @@ class DefaultCardDelegate(
enableLuhnCheck = enableLuhnCheck,
isBrandSupported = !shouldFailWithUnsupportedBrand
),
expiryDateState = validateExpiryDate(inputData.expiryDate, reliableSelectedCard?.expiryDatePolicy),
securityCodeState = validateSecurityCode(inputData.securityCode, reliableSelectedCard),
expiryDateState = validateExpiryDate(inputData.expiryDate, selectedOrFirstCardType?.expiryDatePolicy),
securityCodeState = validateSecurityCode(inputData.securityCode, selectedOrFirstCardType),
holderNameState = validateHolderName(inputData.holderName),
socialSecurityNumberState = validateSocialSecurityNumber(inputData.socialSecurityNumber),
kcpBirthDateOrTaxNumberState = validateKcpBirthDateOrTaxNumber(inputData.kcpBirthDateOrTaxNumber),
kcpCardPasswordState = validateKcpCardPassword(inputData.kcpCardPassword),
addressState = validateAddress(
inputData.address,
addressFormUIState,
reliableSelectedCard,
selectedOrFirstCardType,
updatedCountryOptions,
updatedStateOptions
),
installmentState = makeInstallmentFieldState(inputData.installmentOption),
shouldStorePaymentMethod = inputData.isStorePaymentMethodSwitchChecked,
cvcUIState = makeCvcUIState(reliableSelectedCard?.cvcPolicy),
expiryDateUIState = makeExpiryDateUIState(reliableSelectedCard?.expiryDatePolicy),
cvcUIState = makeCvcUIState(selectedOrFirstCardType),
expiryDateUIState = makeExpiryDateUIState(selectedOrFirstCardType?.expiryDatePolicy),
holderNameUIState = getHolderNameUIState(),
showStorePaymentField = showStorePaymentField(),
detectedCardTypes = filteredDetectedCardTypes,
Expand Down Expand Up @@ -481,14 +479,8 @@ class DefaultCardDelegate(
securityCode: String,
cardType: DetectedCardType?
): FieldState<String> {
return if (isCvcHidden(makeCvcUIState(cardType?.cvcPolicy))) {
FieldState(
securityCode,
Validation.Valid
)
} else {
CardValidationUtils.validateSecurityCode(securityCode, cardType)
}
val cvcUIState = makeCvcUIState(cardType)
return CardValidationUtils.validateSecurityCode(securityCode, cardType, cvcUIState)
}

private fun validateHolderName(holderName: String): FieldState<String> {
Expand Down Expand Up @@ -552,9 +544,7 @@ class DefaultCardDelegate(
}

private fun isCvcHidden(cvcUIState: InputFieldUIState = outputData.cvcUIState): Boolean {
val hiddenAfterBinLookup =
componentParams.cvcVisibility == CVCVisibility.HIDE_FIRST && cvcUIState == InputFieldUIState.HIDDEN
return componentParams.cvcVisibility == CVCVisibility.ALWAYS_HIDE || hiddenAfterBinLookup
return cvcUIState == InputFieldUIState.HIDDEN
}

private fun isSocialSecurityNumberRequired(): Boolean {
Expand Down Expand Up @@ -609,27 +599,35 @@ class DefaultCardDelegate(
)
}

private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy?): InputFieldUIState {
Logger.d(TAG, "makeCvcUIState: $cvcPolicy")
private fun makeCvcUIState(detectedCardType: DetectedCardType?): InputFieldUIState {
Logger.d(TAG, "makeCvcUIState: ${detectedCardType?.cvcPolicy}")

return when (componentParams.cvcVisibility) {
CVCVisibility.ALWAYS_SHOW -> {
when (cvcPolicy) {
Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL
Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN
else -> InputFieldUIState.REQUIRED
return if (detectedCardType?.isReliable == true) {
when (componentParams.cvcVisibility) {
CVCVisibility.ALWAYS_SHOW -> {
when (detectedCardType.cvcPolicy) {
Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL
Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN
else -> InputFieldUIState.REQUIRED
}
}
}

CVCVisibility.HIDE_FIRST -> {
when (cvcPolicy) {
Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED
Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL
else -> InputFieldUIState.HIDDEN
CVCVisibility.HIDE_FIRST -> {
when (detectedCardType.cvcPolicy) {
Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED
Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL
else -> InputFieldUIState.HIDDEN
}
}
}

CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN
CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN
}
} else {
when (componentParams.cvcVisibility) {
CVCVisibility.ALWAYS_SHOW -> InputFieldUIState.REQUIRED
CVCVisibility.HIDE_FIRST -> InputFieldUIState.HIDDEN
CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ internal class StoredCardDelegate(
isReliable = true,
enableLuhnCheck = true,
cvcPolicy = when {
isCvcHidden() -> Brand.FieldPolicy.HIDDEN
componentParams.storedCVCVisibility == StoredCVCVisibility.HIDE ||
noCvcBrands.contains(cardType) -> Brand.FieldPolicy.HIDDEN
else -> Brand.FieldPolicy.REQUIRED
},
expiryDatePolicy = Brand.FieldPolicy.REQUIRED,
Expand Down Expand Up @@ -307,18 +308,12 @@ internal class StoredCardDelegate(
}

private fun validateSecurityCode(securityCode: String, detectedCardType: DetectedCardType): FieldState<String> {
return if (isCvcHidden()) {
FieldState(
securityCode,
Validation.Valid
)
} else {
CardValidationUtils.validateSecurityCode(securityCode, detectedCardType)
}
val cvcUiState = makeCvcUIState(detectedCardType.cvcPolicy)
return CardValidationUtils.validateSecurityCode(securityCode, detectedCardType, cvcUiState)
}

private fun isCvcHidden(): Boolean {
return componentParams.storedCVCVisibility == StoredCVCVisibility.HIDE || noCvcBrands.contains(cardType)
return outputData.cvcUIState == InputFieldUIState.HIDDEN
}

private fun mapComponentState(
Expand Down Expand Up @@ -383,9 +378,10 @@ internal class StoredCardDelegate(

private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy): InputFieldUIState {
Logger.d(TAG, "makeCvcUIState: $cvcPolicy")
return when (componentParams.storedCVCVisibility) {
StoredCVCVisibility.SHOW -> InputFieldUIState.REQUIRED
StoredCVCVisibility.HIDE -> InputFieldUIState.HIDDEN
return when (cvcPolicy) {
Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED
Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL
Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.adyen.checkout.card.R
import com.adyen.checkout.card.internal.data.model.Brand
import com.adyen.checkout.card.internal.data.model.DetectedCardType
import com.adyen.checkout.card.internal.ui.model.ExpiryDate
import com.adyen.checkout.card.internal.ui.model.InputFieldUIState
import com.adyen.checkout.components.core.internal.ui.model.FieldState
import com.adyen.checkout.components.core.internal.ui.model.Validation
import com.adyen.checkout.core.internal.util.StringUtil
Expand Down Expand Up @@ -137,13 +138,18 @@ object CardValidationUtils {
/**
* Validate Security Code.
*/
internal fun validateSecurityCode(securityCode: String, detectedCardType: DetectedCardType?): FieldState<String> {
internal fun validateSecurityCode(
securityCode: String,
detectedCardType: DetectedCardType?,
cvcUIState: InputFieldUIState
): FieldState<String> {
val normalizedSecurityCode = StringUtil.normalize(securityCode)
val length = normalizedSecurityCode.length
val invalidState = Validation.Invalid(R.string.checkout_security_code_not_valid)
val validation = when {
cvcUIState == InputFieldUIState.HIDDEN -> Validation.Valid
!StringUtil.isDigitsAndSeparatorsOnly(normalizedSecurityCode) -> invalidState
detectedCardType?.cvcPolicy?.isRequired() == false && length == 0 -> Validation.Valid
cvcUIState == InputFieldUIState.OPTIONAL && length == 0 -> Validation.Valid
detectedCardType?.cardBrand == CardBrand(cardType = CardType.AMERICAN_EXPRESS) &&
length == AMEX_SECURITY_CODE_SIZE -> Validation.Valid

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ internal object DetectedCardTypesUtils {
}

fun getSelectedOrFirstDetectedCardType(detectedCardTypes: List<DetectedCardType>): DetectedCardType? {
return getSelectedCardType(detectedCardTypes) ?: detectedCardTypes.firstOrNull()
val selectedCardType = getSelectedCardType(detectedCardTypes)
return selectedCardType ?: detectedCardTypes.firstOrNull()
}

fun getSelectedCardType(detectedCardTypes: List<DetectedCardType>): DetectedCardType? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.adyen.checkout.card.R
import com.adyen.checkout.card.internal.data.model.Brand
import com.adyen.checkout.card.internal.data.model.DetectedCardType
import com.adyen.checkout.card.internal.ui.model.ExpiryDate
import com.adyen.checkout.card.internal.ui.model.InputFieldUIState
import com.adyen.checkout.components.core.internal.ui.model.FieldState
import com.adyen.checkout.components.core.internal.ui.model.Validation
import org.junit.jupiter.api.Assertions.assertEquals
Expand Down Expand Up @@ -353,42 +354,51 @@ internal class CardValidationUtilsTest {
@Test
fun `cvc is empty then result should be invalid`() {
val cvc = ""
val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType())
val actual =
CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}

@Test
fun `cvc is 1 digit then result should be invalid`() {
val cvc = "7"
val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType())
val actual =
CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}

@Test
fun `cvc is 2 digits then result should be invalid`() {
val cvc = "12"
val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType())
val actual =
CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}

@Test
fun `cvc is 3 digits then result should be valid`() {
val cvc = "737"
val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType())
val actual =
CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}

@Test
fun `cvc is 4 digits then result should be invalid`() {
val cvc = "8689"
val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType())
val actual =
CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}

@Test
fun `cvc is 6 digits then result should be invalid`() {
val cvc = "457835"
val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType())
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(),
InputFieldUIState.REQUIRED
)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}

Expand All @@ -397,7 +407,8 @@ internal class CardValidationUtilsTest {
val cvc = "737"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS))
getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)),
cvcUIState = InputFieldUIState.REQUIRED
)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}
Expand All @@ -407,15 +418,17 @@ internal class CardValidationUtilsTest {
val cvc = "8689"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS))
getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)),
cvcUIState = InputFieldUIState.REQUIRED
)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}

@Test
fun `cvc has invalid characters then result should be invalid`() {
val cvc = "1%y"
val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType())
val actual =
CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}

Expand All @@ -424,7 +437,8 @@ internal class CardValidationUtilsTest {
val cvc = "546"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED),
cvcUIState = InputFieldUIState.REQUIRED
)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}
Expand All @@ -434,7 +448,8 @@ internal class CardValidationUtilsTest {
val cvc = "345"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL),
cvcUIState = InputFieldUIState.OPTIONAL
)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}
Expand All @@ -444,7 +459,8 @@ internal class CardValidationUtilsTest {
val cvc = "156"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN),
cvcUIState = InputFieldUIState.HIDDEN
)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}
Expand All @@ -454,7 +470,8 @@ internal class CardValidationUtilsTest {
val cvc = "77"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED),
InputFieldUIState.REQUIRED
)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}
Expand All @@ -464,27 +481,30 @@ internal class CardValidationUtilsTest {
val cvc = "9"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL),
InputFieldUIState.OPTIONAL
)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}

@Test
fun `cvc is invalid with field policy hidden then result should be invalid`() {
fun `cvc is invalid with field policy hidden then result should be valid`() {
val cvc = "1358"
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN),
cvcUIState = InputFieldUIState.HIDDEN
)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}

@Test
fun `cvc is empty with field policy required then result should be invalid`() {
val cvc = ""
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED),
cvcUIState = InputFieldUIState.REQUIRED
)
assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual)
}
Expand All @@ -494,7 +514,8 @@ internal class CardValidationUtilsTest {
val cvc = ""
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL),
cvcUIState = InputFieldUIState.OPTIONAL
)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}
Expand All @@ -504,7 +525,8 @@ internal class CardValidationUtilsTest {
val cvc = ""
val actual = CardValidationUtils.validateSecurityCode(
cvc,
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN)
getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN),
cvcUIState = InputFieldUIState.HIDDEN
)
assertEquals(FieldState(cvc, Validation.Valid), actual)
}
Expand Down

0 comments on commit 4bf509f

Please sign in to comment.