diff --git a/README.md b/README.md
index 402ae05..63f9010 100644
--- a/README.md
+++ b/README.md
@@ -327,6 +327,18 @@ minus chars are not allowed at the beginning or end.
public Intervention\Validation\Rules\Username::__construct()
+### VIN (Vehicle Identification Number)
+
+The field under validation must be a valid Vehicle identification number according to ISO-3779.
+
+ public Intervention\Validation\Rules\Vin::__construct(bool $checkDigit = false)
+
+#### Parameters
+
+**checkDigit**
+
+Optional verification according to the North American check system. Default `false`.
+
## Development & Testing
With this package comes a Docker image to build a test suite container. To
diff --git a/phpunit.dist.xml b/phpunit.dist.xml
new file mode 100644
index 0000000..cfa3bc7
--- /dev/null
+++ b/phpunit.dist.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/Intervention/Validation/Validator.php
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
deleted file mode 100644
index a3730b2..0000000
--- a/phpunit.xml.dist
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- ./tests/
-
-
-
-
- ./src/Intervention/Validation/Validator.php
-
-
-
diff --git a/src/Rules/Vin.php b/src/Rules/Vin.php
new file mode 100644
index 0000000..a5c2b97
--- /dev/null
+++ b/src/Rules/Vin.php
@@ -0,0 +1,73 @@
+checkDigit) {
+ return $this->validateCheckDigit((string) $value);
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate the VIN check digit using the North American standard.
+ */
+ private function validateCheckDigit(string $vin): bool
+ {
+ // weights for each position (position 8 has weight 0 as it's the check digit)
+ $weights = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];
+
+ // transliteration values for letters
+ $transliteration = [
+ 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8,
+ 'J' => 1, 'K' => 2, 'L' => 3, 'M' => 4, 'N' => 5, 'P' => 7, 'R' => 9, 'S' => 2,
+ 'T' => 3, 'U' => 4, 'V' => 5, 'W' => 6, 'X' => 7, 'Y' => 8, 'Z' => 9,
+ ];
+
+ $sum = 0;
+ foreach (str_split($vin) as $i => $char) {
+ $value = is_numeric($char) ? (int) $char : $transliteration[$char];
+ $sum += $value * $weights[$i];
+ }
+
+ $checkDigit = $sum % 11;
+ $expectedCheckDigit = $checkDigit === 10 ? 'X' : (string) $checkDigit;
+
+ return $vin[8] === $expectedCheckDigit;
+ }
+}
diff --git a/tests/Rules/VinTest.php b/tests/Rules/VinTest.php
new file mode 100644
index 0000000..2e97e05
--- /dev/null
+++ b/tests/Rules/VinTest.php
@@ -0,0 +1,51 @@
+isValid($value);
+ $this->assertEquals($result, $valid);
+ }
+
+ #[DataProvider('dataProviderCheckDigit')]
+ public function testValidationCheckDigit(bool $result, string $value): void
+ {
+ $valid = (new Vin(true))->isValid($value);
+ $this->assertEquals($result, $valid);
+ }
+
+ public static function dataProvider(): Generator
+ {
+ yield [true, 'WP0ZZZ99ZTS392124'];
+ yield [true, '5GZCZ43D13S812715'];
+ yield [true, 'KLATF08Y1VB363636'];
+ yield [true, 'SGZCZ43D13S812715'];
+ yield [true, 'W0L000051T2123456'];
+ yield [true, 'WDD1690071J236589'];
+ yield [true, 'WVWZZZ1JZ3W386752'];
+ yield [false, 'WVWZZZ1OZ3W386752'];
+ }
+
+ public static function dataProviderCheckDigit(): Generator
+ {
+ yield [true, '5GZCZ43D13S812715'];
+ yield [false, 'WP0ZZZ99ZTS392124'];
+ yield [false, 'KLATF08Y1VB363636'];
+ yield [false, 'SGZCZ43D13S812715'];
+ yield [false, 'W0L000051T2123456'];
+ yield [false, 'WDD1690071J236589'];
+ yield [false, 'WVWZZZ1JZ3W386752'];
+ yield [false, 'WVWZZZ1OZ3W386752'];
+ }
+}