Skip to content

v6 - Improve expiry date input/output transformations and validation#2654

Open
jreij wants to merge 5 commits intomainfrom
chore/expiry-date-input-output-transformations
Open

v6 - Improve expiry date input/output transformations and validation#2654
jreij wants to merge 5 commits intomainfrom
chore/expiry-date-input-output-transformations

Conversation

@jreij
Copy link
Copy Markdown
Collaborator

@jreij jreij commented Mar 26, 2026

Description

  • Improve expiry date input transformation.
  • Improve expiry date output transformation.
  • Simplify expiry date validation.

Checklist

  • Code is unit tested
  • Changes are tested manually

Ticket Number

COSDK-921

@jreij jreij requested a review from a team as a code owner March 26, 2026 11:38
@AdyenAutomationBot AdyenAutomationBot added the Chore [PRs only] Indicates any task that does not need to be mentioned in the public release notes label Mar 26, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors how expiry dates are handled within the card component. The changes aim to improve the robustness of input parsing, provide a clearer user experience through automatic formatting, and simplify the underlying validation logic. By introducing dedicated parsing and transformation utilities and removing an unnecessary data model, the code becomes more maintainable and less prone to parsing errors, ensuring a more reliable and user-friendly expiry date input process.

Highlights

  • Improved Expiry Date Parsing: A new ExpiryDateParser utility has been introduced to centralize and improve the parsing of expiry date strings (MMYY format) into separate month and year components, handling full year conversion and invalid inputs.
  • Enhanced Input and Output Transformations: The ExpiryDateInputTransformation has been refactored to leverage a new DigitOnlyTextFieldBufferTransformation for robust digit-only input handling. A SeparatorsOutputTransformation is now applied in the UI to automatically format the expiry date with a '/' separator for better user experience, while the internal representation remains digit-only.
  • Simplified Expiry Date Validation: The CardExpiryDateValidator has been simplified to directly validate month and year strings, removing its previous dependency on the ExpiryDate data class and streamlining the validation logic.
  • Refactored Expiry Date Model: The ExpiryDate data class and its associated utility functions have been removed from the core API, promoting a more direct and efficient handling of expiry date components as strings.
  • New Expiry Date Properties: A new ExpiryDateProperties object has been added to define constants for expiry date maximum length without separators and the separator character itself, centralizing these configurations.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@jreij jreij changed the title Improve expiry date input/output transformations and validation v6 - Improve expiry date input/output transformations and validation Mar 26, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the handling of expiry dates within the card component. It introduces a new ExpiryDateParser to parse raw 4-digit expiry date strings (MMYY) into separate month and year components. The CardExpiryDateValidator is updated to validate expiry dates using these separate month and year strings, replacing the previous MM/yy string-based validation. The UI input for expiry dates is enhanced with new InputTransformation and OutputTransformation classes to automatically format the input with a '/' separator and ensure only digits are entered. The ExpiryDate data class and related extensions are removed, streamlining the data model. Unit tests have been added for the new ExpiryDateParser and updated for CardExpiryDateValidator and CardComponentStateValidator to reflect the changes in expiry date handling. A review comment suggests that the ExpiryDateParser function should include validation to ensure its input string contains only digits, aligning with its documentation and preventing unexpected behavior.

Comment on lines +21 to +23
if (expiryDate.length != EXPIRY_DATE_MAX_LENGTH_NO_SEPARATORS) {
return null
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The function's KDoc states that it parses "digit only input", but there's no validation to ensure the input string contains only digits. This could lead to unexpected behavior if non-digit characters are passed, as take(2) and takeLast(2) would just take any characters. To make the function more robust and align with its documentation, you should add a check to ensure the input consists only of digits.

Suggested change
if (expiryDate.length != EXPIRY_DATE_MAX_LENGTH_NO_SEPARATORS) {
return null
}
if (expiryDate.length != EXPIRY_DATE_MAX_LENGTH_NO_SEPARATORS || !expiryDate.all { it.isDigit() }) {
return null
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this function is internal, we assume the string has already been validated before being passed here.

@jreij jreij force-pushed the chore/expiry-date-input-output-transformations branch from e6c0207 to da5eef3 Compare March 27, 2026 11:40
@jreij jreij force-pushed the chore/expiry-date-input-output-transformations branch from da5eef3 to 517b843 Compare March 27, 2026 13:39
@github-actions
Copy link
Copy Markdown
Contributor

✅ No public API changes

@sonarqubecloud
Copy link
Copy Markdown

@OscarSpruit OscarSpruit self-assigned this Mar 30, 2026
}
val expiryMonth = expiryDate.take(2)
val shortYear = expiryDate.takeLast(2)
val expiryYear = if (returnFullYear) "$YEAR_PREFIX$shortYear" else shortYear
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work for a long time, but wouldn't it be better to make this dynamic? Afaik SimpleDateFormat automatically handles century rollovers. You could do something like:

val expiryYear = if (returnFullYear) {
    try {
        val date = SimpleDateFormat("yy", Locale.ROOT).parse(shortYear)
        date?.let { SimpleDateFormat("yyyy", Locale.ROOT).format(it) } ?: return null
    } catch (e: ParseException) {
        return null
    }
} else {
    shortYear
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can even replace this whole piece with SimpleDateFormat("MM/yy", Locale.ROOT) and use that to retrieve the month and year

// Calendar months are 0-based (January is 0), so we subtract 1
set(Calendar.MONTH, month - 1)
// add 2000 to the 2-digit year
set(Calendar.YEAR, YEAR_2000 + year)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. The ExpiryDate class has some logic to create the expiry year dynamically. Maybe we could use that for inspiration?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Chore [PRs only] Indicates any task that does not need to be mentioned in the public release notes size:large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants