Skip to content

Commit

Permalink
Merge pull request #1788 from Adyen/feature/french_meal_vouchers
Browse files Browse the repository at this point in the history
French Meal Vouchers
  • Loading branch information
araratthehero authored Oct 8, 2024
2 parents b7ac75c + 0df176d commit ca3d94e
Show file tree
Hide file tree
Showing 157 changed files with 3,832 additions and 498 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ implementation "com.adyen.checkout:card:5.6.0"

The library is available on [Maven Central][mavenRepo].

## UI Customization
## Additional documentation

[See the UI Customization Guide for more.](docs/UI_CUSTOMIZATION.md)
* [UI Customization guide][docs.github.uiCustomization]

* [Additional documentation for payment methods][docs.github.paymentMethods]

## Migrate from v4

Expand Down Expand Up @@ -111,6 +113,8 @@ This repository is available under the [MIT license](LICENSE).
[docs.clientKey]: https://docs.adyen.com/development-resources/client-side-authentication#get-your-client-key
[docs.dropIn]: https://docs.adyen.com/online-payments/build-your-integration/?platform=Android&integration=Drop-in
[docs.components]: https://docs.adyen.com/online-payments/build-your-integration/?platform=Android&integration=Components
[docs.github.uiCustomization]: docs/UI_CUSTOMIZATION.md
[docs.github.paymentMethods]: docs/payment-methods
[mavenRepo]: https://repo1.maven.org/maven2/com/adyen/checkout/
[migration.guide]: https://docs.adyen.com/online-payments/build-your-integration/migrate-to-android-5-0-0
[github.newIssue]: https://github.com/Adyen/adyen-android/issues/new/choose
Expand Down
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
## New
- Added support for 6 more locales: Catalan (ca-ES), Icelandic (is-IS), Bulgarian (bg-BG),
Estonian (et-EE), Latvian (lv-LV) and Lithuanian (lt-lT).
- French meal vouchers are now available with the following payment method types. See the documentation [here](/docs/payment-methods/FRENCH_MEAL_VOUCHER.md).
- Up. Payment method type: **mealVoucher_FR_groupeup**.
- Natixis. Payment method type: **mealVoucher_FR_natixis**.
- Sodexo. Payment method type: **mealVoucher_FR_sodexo**.
- For Twint, storing payment details and paying with them is now supported. See the documentation [here](/docs/payment-methods/TWINT.md).
- You can now use [Adyen Test Cards Android](https://github.com/Adyen/adyen-testcards-android) to prefill test payment method information, making testing your integration easier.

Expand Down
2 changes: 1 addition & 1 deletion bcmc/src/main/res/layout/bcmc_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
android:layout_height="wrap_content"
android:layout_weight="1">

<com.adyen.checkout.card.internal.ui.view.ExpiryDateInput
<com.adyen.checkout.ui.core.internal.ui.view.ExpiryDateInput
android:id="@+id/editText_expiryDate"
style="@style/AdyenCheckout.Card.ExpiryDateInput"
android:nextFocusRight="@+id/editText_securityCode"
Expand Down
36 changes: 1 addition & 35 deletions card/api/card.api
Original file line number Diff line number Diff line change
Expand Up @@ -564,9 +564,6 @@ public final class com/adyen/checkout/card/internal/provider/CardComponentProvid
public final class com/adyen/checkout/card/internal/ui/DefaultCardDelegate$Companion {
}

public final class com/adyen/checkout/card/internal/ui/model/ExpiryDate$Companion {
}

public final class com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams$CardBasedInstallmentOptions : com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams {
public fun <init> (Ljava/util/List;ZLcom/adyen/checkout/card/CardBrand;)V
public final fun component1 ()Ljava/util/List;
Expand Down Expand Up @@ -595,13 +592,8 @@ public final class com/adyen/checkout/card/internal/ui/model/InstallmentOptionPa
public fun toString ()Ljava/lang/String;
}

public class com/adyen/checkout/card/internal/ui/view/CardNumberInput : com/adyen/checkout/ui/core/internal/ui/view/AdyenTextInputEditText {
public final class com/adyen/checkout/card/internal/ui/view/CardNumberInput : com/adyen/checkout/ui/core/internal/ui/view/AdyenTextInputEditText {
public static final field Companion Lcom/adyen/checkout/card/internal/ui/view/CardNumberInput$Companion;
public fun <init> (Landroid/content/Context;)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
protected fun afterTextChanged (Landroid/text/Editable;)V
public fun getRawValue ()Ljava/lang/String;
public final fun setAmexCardFormat (Z)V
}
Expand All @@ -612,29 +604,3 @@ public final class com/adyen/checkout/card/internal/ui/view/CardNumberInput$Comp
public final class com/adyen/checkout/card/internal/ui/view/CardView$Companion {
}

public final class com/adyen/checkout/card/internal/ui/view/ExpiryDateInput : com/adyen/checkout/ui/core/internal/ui/view/AdyenTextInputEditText {
public static final field Companion Lcom/adyen/checkout/card/internal/ui/view/ExpiryDateInput$Companion;
public static final field SEPARATOR Ljava/lang/String;
public fun <init> (Landroid/content/Context;)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun afterTextChanged (Landroid/text/Editable;)V
public final fun getDate ()Lcom/adyen/checkout/card/internal/ui/model/ExpiryDate;
public final fun setDate (Lcom/adyen/checkout/card/internal/ui/model/ExpiryDate;)V
}

public final class com/adyen/checkout/card/internal/ui/view/ExpiryDateInput$Companion {
}

public final class com/adyen/checkout/card/internal/ui/view/SecurityCodeInput : com/adyen/checkout/card/internal/ui/view/CardNumberInput {
public static final field Companion Lcom/adyen/checkout/card/internal/ui/view/SecurityCodeInput$Companion;
public fun <init> (Landroid/content/Context;)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public final class com/adyen/checkout/card/internal/ui/view/SecurityCodeInput$Companion {
}

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.adyen.checkout.card.internal.ui.model.CardComponentParams
import com.adyen.checkout.card.internal.ui.model.CardInputData
import com.adyen.checkout.card.internal.ui.model.CardListItem
import com.adyen.checkout.card.internal.ui.model.CardOutputData
import com.adyen.checkout.card.internal.ui.model.ExpiryDate
import com.adyen.checkout.card.internal.ui.model.InputFieldUIState
import com.adyen.checkout.card.internal.ui.model.InstallmentParams
import com.adyen.checkout.card.internal.ui.view.InstallmentModel
Expand Down Expand Up @@ -73,6 +72,7 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler
import com.adyen.checkout.ui.core.internal.ui.model.AddressListItem
import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData
import com.adyen.checkout.ui.core.internal.ui.model.AddressParams
import com.adyen.checkout.ui.core.internal.ui.model.ExpiryDate
import com.adyen.checkout.ui.core.internal.util.AddressFormUtils
import com.adyen.checkout.ui.core.internal.util.AddressValidationUtils
import com.adyen.checkout.ui.core.internal.util.SocialSecurityNumberUtils
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import com.adyen.checkout.card.internal.data.model.DetectedCardType
import com.adyen.checkout.card.internal.ui.model.CardComponentParams
import com.adyen.checkout.card.internal.ui.model.CardInputData
import com.adyen.checkout.card.internal.ui.model.CardOutputData
import com.adyen.checkout.card.internal.ui.model.ExpiryDate
import com.adyen.checkout.card.internal.ui.model.InputFieldUIState
import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility
import com.adyen.checkout.card.internal.util.CardValidationUtils
Expand Down Expand Up @@ -56,6 +55,7 @@ import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIEvent
import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState
import com.adyen.checkout.ui.core.internal.ui.SubmitHandler
import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData
import com.adyen.checkout.ui.core.internal.ui.model.ExpiryDate
import com.adyen.checkout.ui.core.internal.util.AddressValidationUtils
import com.adyen.threeds2.ThreeDS2Service
import kotlinx.coroutines.CoroutineScope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.adyen.checkout.card.internal.ui.view.InstallmentModel
import com.adyen.checkout.components.core.internal.ui.model.AddressInputModel
import com.adyen.checkout.components.core.internal.ui.model.InputData
import com.adyen.checkout.ui.core.internal.ui.model.AddressLookupInputData
import com.adyen.checkout.ui.core.internal.ui.model.ExpiryDate

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class CardInputData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.adyen.checkout.components.core.internal.ui.model.FieldState
import com.adyen.checkout.components.core.internal.ui.model.OutputData
import com.adyen.checkout.ui.core.internal.ui.AddressFormUIState
import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData
import com.adyen.checkout.ui.core.internal.ui.model.ExpiryDate

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class CardOutputData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@ import android.text.Editable
import android.text.InputType
import android.text.method.DigitsKeyListener
import android.util.AttributeSet
import androidx.annotation.RestrictTo
import com.adyen.checkout.card.internal.util.CardNumberUtils
import com.adyen.checkout.card.internal.util.CardValidationUtils
import com.adyen.checkout.ui.core.internal.ui.view.AdyenTextInputEditText

/**
* Input that support formatting for card number.
*/
open class CardNumberInput @JvmOverloads constructor(
class CardNumberInput
@JvmOverloads
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
AdyenTextInputEditText(context, attrs, defStyleAttr) {
) : AdyenTextInputEditText(context, attrs, defStyleAttr) {

private var isAmexCard = false
override val rawValue: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import com.adyen.checkout.card.internal.data.model.DetectedCardType
import com.adyen.checkout.card.internal.ui.CardDelegate
import com.adyen.checkout.card.internal.ui.model.CardListItem
import com.adyen.checkout.card.internal.ui.model.CardOutputData
import com.adyen.checkout.card.internal.ui.model.ExpiryDate
import com.adyen.checkout.card.internal.ui.model.InputFieldUIState
import com.adyen.checkout.card.internal.util.InstallmentUtils
import com.adyen.checkout.components.core.internal.ui.ComponentDelegate
Expand All @@ -43,8 +42,10 @@ import com.adyen.checkout.ui.core.internal.ui.AddressFormUIState
import com.adyen.checkout.ui.core.internal.ui.ComponentView
import com.adyen.checkout.ui.core.internal.ui.loadLogo
import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData
import com.adyen.checkout.ui.core.internal.ui.model.ExpiryDate
import com.adyen.checkout.ui.core.internal.ui.view.AdyenTextInputEditText
import com.adyen.checkout.ui.core.internal.ui.view.RoundCornerImageView
import com.adyen.checkout.ui.core.internal.ui.view.SecurityCodeInput
import com.adyen.checkout.ui.core.internal.util.hideError
import com.adyen.checkout.ui.core.internal.util.isVisible
import com.adyen.checkout.ui.core.internal.util.setLocalizedHintFromStyle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.adyen.checkout.core.internal.util.BuildUtils
import com.adyen.checkout.ui.core.internal.ui.ComponentView
import com.adyen.checkout.ui.core.internal.ui.loadLogo
import com.adyen.checkout.ui.core.internal.ui.view.RoundCornerImageView
import com.adyen.checkout.ui.core.internal.ui.view.SecurityCodeInput
import com.adyen.checkout.ui.core.internal.util.hideError
import com.adyen.checkout.ui.core.internal.util.setLocalizedHintFromStyle
import com.adyen.checkout.ui.core.internal.util.showError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import com.adyen.checkout.card.CardType
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
import com.adyen.checkout.ui.core.internal.ui.model.ExpiryDate
import com.adyen.checkout.ui.core.internal.util.ExpiryDateValidationResult
import com.adyen.checkout.ui.core.internal.util.ExpiryDateValidationUtils
import java.util.Calendar
import java.util.GregorianCalendar

Expand All @@ -37,11 +39,6 @@ object CardValidationUtils {
private const val GENERAL_CARD_SECURITY_CODE_SIZE = 3
private const val AMEX_SECURITY_CODE_SIZE = 4

// Date
private const val MONTHS_IN_YEAR = 12
private const val MAXIMUM_YEARS_IN_FUTURE = 30
private const val MAXIMUM_EXPIRED_MONTHS = 3

/**
* Validate card number.
*/
Expand Down Expand Up @@ -80,59 +77,41 @@ object CardValidationUtils {
/**
* Validate Expiry Date.
*/
fun validateExpiryDate(expiryDate: ExpiryDate, fieldPolicy: Brand.FieldPolicy?): FieldState<ExpiryDate> {
return validateExpiryDate(expiryDate, fieldPolicy, GregorianCalendar.getInstance())
}

@VisibleForTesting
internal fun validateExpiryDate(
expiryDate: ExpiryDate,
fieldPolicy: Brand.FieldPolicy?,
calendar: Calendar
calendar: Calendar = GregorianCalendar.getInstance()
): FieldState<ExpiryDate> {
val invalidState = FieldState(expiryDate, Validation.Invalid(R.string.checkout_expiry_date_not_valid))
return when {
dateExists(expiryDate) -> {
val isInMaxYearRange = isInMaxYearRange(expiryDate, calendar)
val isInMinMonthRange = isInMinMonthRange(expiryDate, calendar)
val fieldState = when {
// higher than maxPast and lower than maxFuture
isInMinMonthRange && isInMaxYearRange -> FieldState(expiryDate, Validation.Valid)
!isInMaxYearRange -> FieldState(
expiryDate,
Validation.Invalid(R.string.checkout_expiry_date_not_valid_too_far_in_future)
)

!isInMinMonthRange -> FieldState(
expiryDate,
Validation.Invalid(R.string.checkout_expiry_date_not_valid_too_old)
)

else -> invalidState
}
fieldState
}
val expiryDateValidationResult =
ExpiryDateValidationUtils.validateExpiryDate(expiryDate, calendar)
val validation = generateExpiryDateValidation(fieldPolicy, expiryDateValidationResult)

fieldPolicy?.isRequired() == false && expiryDate != ExpiryDate.INVALID_DATE -> {
FieldState(expiryDate, Validation.Valid)
}

else -> invalidState
}
return FieldState(expiryDate, validation)
}

private fun isInMaxYearRange(expiryDate: ExpiryDate, calendar: Calendar): Boolean {
val expiryDateCalendar = getExpiryCalendar(expiryDate)
val maxFutureCalendar = calendar.clone() as GregorianCalendar
maxFutureCalendar.add(Calendar.YEAR, MAXIMUM_YEARS_IN_FUTURE)
return expiryDateCalendar.get(Calendar.YEAR) <= maxFutureCalendar.get(Calendar.YEAR)
}
@VisibleForTesting
internal fun generateExpiryDateValidation(
fieldPolicy: Brand.FieldPolicy?,
expiryDateValidationResult: ExpiryDateValidationResult,
): Validation {
return when (expiryDateValidationResult) {
ExpiryDateValidationResult.VALID -> Validation.Valid

ExpiryDateValidationResult.INVALID_TOO_FAR_IN_THE_FUTURE ->
Validation.Invalid(R.string.checkout_expiry_date_not_valid_too_far_in_future)

ExpiryDateValidationResult.INVALID_TOO_OLD ->
Validation.Invalid(R.string.checkout_expiry_date_not_valid_too_old)

ExpiryDateValidationResult.INVALID_DATE_FORMAT ->
Validation.Invalid(R.string.checkout_expiry_date_not_valid)

private fun isInMinMonthRange(expiryDate: ExpiryDate, calendar: Calendar): Boolean {
val expiryDateCalendar = getExpiryCalendar(expiryDate)
val maxPastCalendar = calendar.clone() as GregorianCalendar
maxPastCalendar.add(Calendar.MONTH, -MAXIMUM_EXPIRED_MONTHS)
return expiryDateCalendar >= maxPastCalendar
ExpiryDateValidationResult.INVALID_OTHER_REASON -> if (fieldPolicy?.isRequired() == false) {
Validation.Valid
} else {
Validation.Invalid(R.string.checkout_expiry_date_not_valid)
}
}
}

/**
Expand Down Expand Up @@ -160,29 +139,6 @@ object CardValidationUtils {
}
return FieldState(normalizedSecurityCode, validation)
}

private fun dateExists(expiryDate: ExpiryDate): Boolean {
return (
expiryDate !== ExpiryDate.EMPTY_DATE &&
isValidMonth(expiryDate.expiryMonth) &&
expiryDate.expiryYear > 0
)
}

private fun isValidMonth(month: Int): Boolean {
return month in 1..MONTHS_IN_YEAR
}

private fun getExpiryCalendar(expiryDate: ExpiryDate): Calendar {
val expiryCalendar = GregorianCalendar.getInstance()
expiryCalendar.clear()
// First day of the expiry month. Calendar.MONTH is zero-based.
expiryCalendar[expiryDate.expiryYear, expiryDate.expiryMonth - 1] = 1
// Go to next month and remove 1 day to be on the last day of the expiry month.
expiryCalendar.add(Calendar.MONTH, 1)
expiryCalendar.add(Calendar.DAY_OF_MONTH, -1)
return expiryCalendar
}
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand Down
4 changes: 2 additions & 2 deletions card/src/main/res/layout/card_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
android:layout_marginEnd="@dimen/standard_half_margin"
android:layout_weight="1">

<com.adyen.checkout.card.internal.ui.view.ExpiryDateInput
<com.adyen.checkout.ui.core.internal.ui.view.ExpiryDateInput
android:id="@+id/editText_expiryDate"
style="@style/AdyenCheckout.Card.ExpiryDateInput"
android:nextFocusDown="@id/editText_securityCode"
Expand All @@ -115,7 +115,7 @@
android:layout_marginStart="@dimen/standard_half_margin"
android:layout_weight="1">

<com.adyen.checkout.card.internal.ui.view.SecurityCodeInput
<com.adyen.checkout.ui.core.internal.ui.view.SecurityCodeInput
android:id="@+id/editText_securityCode"
style="@style/AdyenCheckout.Card.SecurityCodeInput"
android:nextFocusDown="@id/editText_cardHolder"
Expand Down
4 changes: 2 additions & 2 deletions card/src/main/res/layout/stored_card_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
android:layout_marginEnd="@dimen/standard_half_margin"
android:layout_weight="1">

<com.adyen.checkout.card.internal.ui.view.ExpiryDateInput
<com.adyen.checkout.ui.core.internal.ui.view.ExpiryDateInput
android:id="@+id/editText_expiryDate"
style="@style/AdyenCheckout.Card.ExpiryDateInput"
android:enabled="false"
Expand All @@ -89,7 +89,7 @@
android:layout_marginStart="@dimen/standard_half_margin"
android:layout_weight="1">

<com.adyen.checkout.card.internal.ui.view.SecurityCodeInput
<com.adyen.checkout.ui.core.internal.ui.view.SecurityCodeInput
android:id="@+id/editText_securityCode"
style="@style/AdyenCheckout.Card.SecurityCodeInput"
android:nextFocusDown="@id/editText_cardHolder"
Expand Down
Loading

0 comments on commit ca3d94e

Please sign in to comment.