Skip to content

Commit 51e1d84

Browse files
implement iban validators for the new api
1 parent 5cd2eb8 commit 51e1d84

File tree

5 files changed

+206
-4
lines changed

5 files changed

+206
-4
lines changed

README-updated.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ TextFormField(
149149

150150
### Finance validators
151151

152-
- `FormBuilderValidators.bic()`: Checks if the field contains a valid BIC (Bank Identifier Code).
153-
- TODO [ ] `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN.
152+
- `Validators.bic()`: Checks if the field contains a valid BIC (Bank Identifier Code).
153+
- `Validators.iban()` - Checks if the field contains a valid IBAN (International Bank Account Number).
154154

155155
### Generic Type Validators
156156
Validators that check a generic type user input.

doc/migration.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,16 @@ Validators.creditCard(
660660
);
661661
662662
```
663-
TODO continue from here!!!!
664663

665-
- `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN.
664+
- `FormBuilderValidators.iban()`
665+
```dart
666+
// Old API:
667+
// OBS.: There is a bug in the regex parameter. It is not used at all.
668+
FormBuilderValidators.iban(errorText: 'invalid iban');
669+
670+
// New API:
671+
Validators.iban(ibanMsg: (_)=>'invalid iban');
672+
```
666673

667674
### Identity validators
668675

lib/src/form_builder_validators.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4106,6 +4106,54 @@ final class Validators {
41064106
}) =>
41074107
val.creditCard(regex: regex, creditCardMsg: creditCardMsg);
41084108

4109+
///{@template validator_iban}
4110+
/// A validator function that checks if a string is a valid International Bank
4111+
/// Account Number (IBAN).
4112+
///
4113+
/// Returns null if the input is a valid IBAN format, otherwise returns an
4114+
/// error message. The validator performs standard IBAN validation including
4115+
/// length check, character conversion, and checksum calculation according to
4116+
/// the ISO 13616 standard.
4117+
///
4118+
/// ## Parameters
4119+
/// - `isIban` (`bool Function(String input)?`): Optional custom validation
4120+
/// function that determines if the input is a valid IBAN. If provided, this
4121+
/// function overrides the default validation logic.
4122+
/// - `ibanMsg` (`String Function(String input)?`): Optional function that
4123+
/// returns a custom error message when validation fails. If not provided,
4124+
/// the default localized error message is used.
4125+
///
4126+
/// ## Returns
4127+
/// A `Validator<String>` function that accepts a string input and returns
4128+
/// null for valid IBANs or an error message string for invalid IBANs.
4129+
///
4130+
/// ## Examples
4131+
/// ```dart
4132+
/// // Basic usage with default validation and error message
4133+
/// final validator = Validators.iban();
4134+
/// assert(validator('GB82 WEST 1234 5698 7654 32') == null); // Valid IBAN
4135+
/// assert(validator('invalid123') != null); // Invalid IBAN
4136+
///
4137+
/// // Using custom validation logic
4138+
/// final customValidator = FormBuilderValidators.iban(
4139+
/// isIban: (input) => input.startsWith('DE'),
4140+
/// ibanMsg: (input) => 'Only German IBANs are accepted',
4141+
/// );
4142+
/// assert(customValidator('DE89 3704 0044 0532 0130 00') == null); // Valid German IBAN
4143+
/// assert(customValidator('GB82 WEST 1234 5698 7654 32') != null); // Not a German IBAN
4144+
/// ```
4145+
///
4146+
/// ## Caveats
4147+
/// - The validator removes all spaces from the input before validation
4148+
/// - The validation is case-insensitive as the input is converted to uppercase
4149+
/// - The minimum length check for IBANs is set to 15 characters (after removing spaces)
4150+
/// {@endtemplate}
4151+
static Validator<String> iban({
4152+
c.bool Function(String input)? isIban,
4153+
String Function(String input)? ibanMsg,
4154+
}) =>
4155+
val.iban(isIban: isIban, ibanMsg: ibanMsg);
4156+
41094157
///{@template validator_bic}
41104158
/// Creates a validator that checks if a string is a valid BIC (Bank Identifier Code).
41114159
///

lib/src/validators/finance_validators.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ Validator<String> bic({
3131
};
3232
}
3333

34+
/// {@macro validator_iban}
35+
Validator<String> iban({
36+
bool Function(String input)? isIban,
37+
String Function(String input)? ibanMsg,
38+
}) {
39+
return (String input) {
40+
return isIban?.call(input) ?? _isIBAN(input)
41+
? null
42+
: (ibanMsg?.call(input) ??
43+
FormBuilderLocalizations.current.ibanErrorText);
44+
};
45+
}
46+
3447
//******************************************************************************
3548
//* Aux functions *
3649
//******************************************************************************
@@ -82,3 +95,32 @@ bool _isBIC(String value) {
8295

8396
return regex.hasMatch(bic);
8497
}
98+
99+
/// Check if the string is a valid IBAN.
100+
bool _isIBAN(String value) {
101+
final String iban = value.replaceAll(' ', '').toUpperCase();
102+
103+
if (iban.length < 15) {
104+
return false;
105+
}
106+
107+
final String rearranged = iban.substring(4) + iban.substring(0, 4);
108+
final String numericIban = rearranged.split('').map((String char) {
109+
final int charCode = char.codeUnitAt(0);
110+
return charCode >= 65 && charCode <= 90 ? (charCode - 55).toString() : char;
111+
}).join();
112+
113+
int remainder = int.parse(numericIban.substring(0, 9)) % 97;
114+
for (int i = 9; i < numericIban.length; i += 7) {
115+
remainder = int.parse(
116+
remainder.toString() +
117+
numericIban.substring(
118+
i,
119+
i + 7 < numericIban.length ? i + 7 : numericIban.length,
120+
),
121+
) %
122+
97;
123+
}
124+
125+
return remainder == 1;
126+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import 'package:faker_dart/faker_dart.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:form_builder_validators/localization/l10n.dart';
4+
import 'package:form_builder_validators/src/validators/constants.dart';
5+
import 'package:form_builder_validators/src/validators/finance_validators.dart';
6+
7+
void main() {
8+
final Faker faker = Faker.instance;
9+
final String customErrorMessage = faker.lorem.sentence();
10+
11+
group('IbanValidator -', () {
12+
test('should return null if the IBAN is valid', () {
13+
// Arrange
14+
final Validator<String> validator = iban();
15+
const String validIban = 'DE89370400440532013000';
16+
17+
// Act
18+
final String? result = validator(validIban);
19+
20+
// Assert
21+
expect(result, isNull);
22+
});
23+
24+
test('should return null if the IBAN is valid with spaces', () {
25+
// Arrange
26+
final Validator<String> validator = iban();
27+
const String validIban = 'DE89 3704 0044 0532 0130 00';
28+
29+
// Act
30+
final String? result = validator(validIban);
31+
32+
// Assert
33+
expect(result, isNull);
34+
});
35+
36+
test('should return the default error message if the IBAN is invalid', () {
37+
// Arrange
38+
final Validator<String> validator = iban();
39+
const String invalidIban = 'DE89370400440532013001';
40+
41+
// Act
42+
final String? result = validator(invalidIban);
43+
44+
// Assert
45+
expect(result, equals(FormBuilderLocalizations.current.ibanErrorText));
46+
});
47+
48+
test(
49+
'should return the custom error message if the IBAN Validator<String> is invalid',
50+
() {
51+
// Arrange
52+
final Validator<String> validator =
53+
iban(ibanMsg: (_) => customErrorMessage);
54+
const String invalidIban = 'DE89370400440532013001';
55+
56+
// Act
57+
final String? result = validator(invalidIban);
58+
59+
// Assert
60+
expect(result, equals(customErrorMessage));
61+
});
62+
63+
test(
64+
'should return the default error message if the value is an empty string',
65+
() {
66+
// Arrange
67+
final Validator<String> validator = iban();
68+
const String value = '';
69+
70+
// Act
71+
final String? result = validator(value);
72+
73+
// Assert
74+
expect(result, equals(FormBuilderLocalizations.current.ibanErrorText));
75+
});
76+
77+
test(
78+
'should return the default error message if the IBAN length is less than 15 characters',
79+
() {
80+
// Arrange
81+
final Validator<String> validator = iban();
82+
const String shortIban = 'DE8937040044';
83+
84+
// Act
85+
final String? result = validator(shortIban);
86+
87+
// Assert
88+
expect(result, equals(FormBuilderLocalizations.current.ibanErrorText));
89+
});
90+
91+
test(
92+
'should return null if the IBAValidator<String> N length is exactly 15 characters and valid',
93+
() {
94+
// Arrange
95+
final Validator<String> validator = iban();
96+
const String validShortIban = 'AL47212110090000000235698741';
97+
98+
// Act
99+
final String? result = validator(validShortIban);
100+
101+
// Assert
102+
expect(result, isNull);
103+
});
104+
});
105+
}

0 commit comments

Comments
 (0)