Skip to content

Commit

Permalink
Merge pull request #332 from Adyen/feature/exposeCardValidators
Browse files Browse the repository at this point in the history
Add support for card validators
  • Loading branch information
Robert-SD authored Jan 13, 2025
2 parents e8f2086 + 2043a8d commit b3d41b7
Show file tree
Hide file tree
Showing 22 changed files with 1,025 additions and 13 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.3.0 (NEXT RELEASE)

### New

- Added card validators (Card number validation, card expiry date validation, card security code validation).

## 1.2.0

### New
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.adyen.checkout.flutter

import CardComponentConfigurationDTO
import CardExpiryDateValidationResultDTO
import CardNumberValidationResultDTO
import CardSecurityCodeValidationResultDTO
import CheckoutPlatformInterface
import DropInConfigurationDTO
import EncryptedCardDTO
Expand All @@ -16,7 +19,8 @@ import com.adyen.checkout.components.core.PaymentMethodsApiResponse
import com.adyen.checkout.components.core.internal.Configuration
import com.adyen.checkout.core.AdyenLogger
import com.adyen.checkout.core.internal.util.Logger.NONE
import com.adyen.checkout.flutter.cse.AdyenCSE
import com.adyen.checkout.flutter.apiOnly.AdyenCSE
import com.adyen.checkout.flutter.apiOnly.CardValidation
import com.adyen.checkout.flutter.session.SessionHolder
import com.adyen.checkout.flutter.utils.ConfigurationMapper.mapToAnalyticsConfiguration
import com.adyen.checkout.flutter.utils.ConfigurationMapper.mapToDropInConfiguration
Expand All @@ -36,8 +40,6 @@ class CheckoutPlatformApi(
private val activity: FragmentActivity,
private val sessionHolder: SessionHolder,
) : CheckoutPlatformInterface {
private val adyenCSE: AdyenCSE = AdyenCSE()

override fun getReturnUrl(callback: (Result<String>) -> Unit) {
callback(Result.success(RedirectComponent.getReturnUrl(activity.applicationContext)))
}
Expand Down Expand Up @@ -73,7 +75,7 @@ class CheckoutPlatformApi(
publicKey: String,
callback: (Result<EncryptedCardDTO>) -> Unit
) {
val encryptedCardResult = adyenCSE.encryptCard(unencryptedCardDTO, publicKey)
val encryptedCardResult = AdyenCSE.encryptCard(unencryptedCardDTO, publicKey)
callback(encryptedCardResult)
}

Expand All @@ -82,10 +84,25 @@ class CheckoutPlatformApi(
publicKey: String,
callback: (Result<String>) -> Unit
) {
val encryptedBin = adyenCSE.encryptBin(bin, publicKey)
val encryptedBin = AdyenCSE.encryptBin(bin, publicKey)
callback(encryptedBin)
}

override fun validateCardNumber(
cardNumber: String,
enableLuhnCheck: Boolean
): CardNumberValidationResultDTO = CardValidation.validateCardNumber(cardNumber, enableLuhnCheck)

override fun validateCardExpiryDate(
expiryMonth: String,
expiryYear: String
): CardExpiryDateValidationResultDTO = CardValidation.validateCardExpiryDate(expiryMonth, expiryYear)

override fun validateCardSecurityCode(
securityCode: String,
cardBrandTxVariant: String?
): CardSecurityCodeValidationResultDTO = CardValidation.validateCardSecurityCode(securityCode, cardBrandTxVariant)

private fun determineSessionConfiguration(configuration: Any?): Configuration? {
when (configuration) {
is DropInConfigurationDTO -> {
Expand Down
100 changes: 100 additions & 0 deletions android/src/main/kotlin/com/adyen/checkout/flutter/PlatformApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,46 @@ enum class ApplePaySummaryItemType(val raw: Int) {
}
}

enum class CardNumberValidationResultDTO(val raw: Int) {
VALID(0),
INVALIDILLEGALCHARACTERS(1),
INVALIDLUHNCHECK(2),
INVALIDTOOSHORT(3),
INVALIDTOOLONG(4),
INVALIDOTHERREASON(5);

companion object {
fun ofRaw(raw: Int): CardNumberValidationResultDTO? {
return values().firstOrNull { it.raw == raw }
}
}
}

enum class CardExpiryDateValidationResultDTO(val raw: Int) {
VALID(0),
INVALIDTOOFARINTHEFUTURE(1),
INVALIDTOOOLD(2),
NONPARSEABLEDATE(3),
INVALIDOTHERREASON(4);

companion object {
fun ofRaw(raw: Int): CardExpiryDateValidationResultDTO? {
return values().firstOrNull { it.raw == raw }
}
}
}

enum class CardSecurityCodeValidationResultDTO(val raw: Int) {
VALID(0),
INVALID(1);

companion object {
fun ofRaw(raw: Int): CardSecurityCodeValidationResultDTO? {
return values().firstOrNull { it.raw == raw }
}
}
}

/** Generated class from Pigeon that represents data sent in messages. */
data class SessionDTO (
val id: String,
Expand Down Expand Up @@ -1485,6 +1525,9 @@ interface CheckoutPlatformInterface {
fun clearSession()
fun encryptCard(unencryptedCardDTO: UnencryptedCardDTO, publicKey: String, callback: (Result<EncryptedCardDTO>) -> Unit)
fun encryptBin(bin: String, publicKey: String, callback: (Result<String>) -> Unit)
fun validateCardNumber(cardNumber: String, enableLuhnCheck: Boolean): CardNumberValidationResultDTO
fun validateCardExpiryDate(expiryMonth: String, expiryYear: String): CardExpiryDateValidationResultDTO
fun validateCardSecurityCode(securityCode: String, cardBrandTxVariant: String?): CardSecurityCodeValidationResultDTO
fun enableConsoleLogging(loggingEnabled: Boolean)

companion object {
Expand Down Expand Up @@ -1594,6 +1637,63 @@ interface CheckoutPlatformInterface {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.adyen_checkout.CheckoutPlatformInterface.validateCardNumber", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val cardNumberArg = args[0] as String
val enableLuhnCheckArg = args[1] as Boolean
var wrapped: List<Any?>
try {
wrapped = listOf<Any?>(api.validateCardNumber(cardNumberArg, enableLuhnCheckArg).raw)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.adyen_checkout.CheckoutPlatformInterface.validateCardExpiryDate", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val expiryMonthArg = args[0] as String
val expiryYearArg = args[1] as String
var wrapped: List<Any?>
try {
wrapped = listOf<Any?>(api.validateCardExpiryDate(expiryMonthArg, expiryYearArg).raw)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.adyen_checkout.CheckoutPlatformInterface.validateCardSecurityCode", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val securityCodeArg = args[0] as String
val cardBrandTxVariantArg = args[1] as String?
var wrapped: List<Any?>
try {
wrapped = listOf<Any?>(api.validateCardSecurityCode(securityCodeArg, cardBrandTxVariantArg).raw)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.adyen_checkout.CheckoutPlatformInterface.enableConsoleLogging", codec)
if (api != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.adyen.checkout.flutter.cse
package com.adyen.checkout.flutter.apiOnly

import EncryptedCardDTO
import UnencryptedCardDTO
Expand All @@ -7,7 +7,7 @@ import com.adyen.checkout.flutter.utils.ConfigurationMapper.fromDTO
import com.adyen.checkout.flutter.utils.ConfigurationMapper.mapToEncryptedCardDTO
import java.lang.Exception

internal class AdyenCSE {
internal object AdyenCSE {
fun encryptCard(
unencryptedCardDTO: UnencryptedCardDTO,
publicKey: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.adyen.checkout.flutter.apiOnly

import CardExpiryDateValidationResultDTO
import CardNumberValidationResultDTO
import CardSecurityCodeValidationResultDTO
import com.adyen.checkout.core.CardBrand
import com.adyen.checkout.core.ui.model.ExpiryDate
import com.adyen.checkout.core.ui.validation.CardExpiryDateValidationResult
import com.adyen.checkout.core.ui.validation.CardExpiryDateValidator
import com.adyen.checkout.core.ui.validation.CardNumberValidationResult
import com.adyen.checkout.core.ui.validation.CardNumberValidator
import com.adyen.checkout.core.ui.validation.CardSecurityCodeValidationResult
import com.adyen.checkout.core.ui.validation.CardSecurityCodeValidator

internal object CardValidation {
fun validateCardNumber(
cardNumber: String,
enableLuhnCheck: Boolean
): CardNumberValidationResultDTO {
val validationResult = CardNumberValidator.validateCardNumber(cardNumber, enableLuhnCheck)
return when (validationResult) {
is CardNumberValidationResult.Valid -> CardNumberValidationResultDTO.VALID
is CardNumberValidationResult.Invalid.IllegalCharacters ->
CardNumberValidationResultDTO.INVALIDILLEGALCHARACTERS

is CardNumberValidationResult.Invalid.TooLong -> CardNumberValidationResultDTO.INVALIDTOOLONG
is CardNumberValidationResult.Invalid.TooShort -> CardNumberValidationResultDTO.INVALIDTOOSHORT
is CardNumberValidationResult.Invalid.LuhnCheck -> CardNumberValidationResultDTO.INVALIDLUHNCHECK
else -> CardNumberValidationResultDTO.INVALIDOTHERREASON
}
}

fun validateCardExpiryDate(
expiryMonth: String,
expiryYear: String
): CardExpiryDateValidationResultDTO {
val expireMonth = expiryMonth.toIntOrNull() ?: return CardExpiryDateValidationResultDTO.NONPARSEABLEDATE
val expireYear = expiryYear.toIntOrNull() ?: return CardExpiryDateValidationResultDTO.NONPARSEABLEDATE
val expiryDate = ExpiryDate(expireMonth, expireYear)
val validationResult = CardExpiryDateValidator.validateExpiryDate(expiryDate)
return when (validationResult) {
is CardExpiryDateValidationResult.Valid -> CardExpiryDateValidationResultDTO.VALID
is CardExpiryDateValidationResult.Invalid.TooFarInTheFuture ->
CardExpiryDateValidationResultDTO.INVALIDTOOFARINTHEFUTURE
is CardExpiryDateValidationResult.Invalid.TooOld -> CardExpiryDateValidationResultDTO.INVALIDTOOOLD
is CardExpiryDateValidationResult.Invalid.NonParseableDate ->
CardExpiryDateValidationResultDTO.NONPARSEABLEDATE
else -> CardExpiryDateValidationResultDTO.INVALIDOTHERREASON
}
}

fun validateCardSecurityCode(
securityCode: String,
cardBrandTxVariant: String?
): CardSecurityCodeValidationResultDTO {
val cardBrand = cardBrandTxVariant?.let { CardBrand(it) }
val validationResult = CardSecurityCodeValidator.validateSecurityCode(securityCode, cardBrand)
return when (validationResult) {
is CardSecurityCodeValidationResult.Valid -> CardSecurityCodeValidationResultDTO.VALID
is CardSecurityCodeValidationResult.Invalid -> CardSecurityCodeValidationResultDTO.INVALID
}
}
}
Loading

0 comments on commit b3d41b7

Please sign in to comment.