Skip to content

Commit aaa2b80

Browse files
committed
Payment cards tools including ISO8583 parser and Mastercard IPM files processing
0 parents  commit aaa2b80

File tree

18 files changed

+1806
-0
lines changed

18 files changed

+1806
-0
lines changed

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
# Maven
3+
target/
4+
pom.xml.tag
5+
pom.xml.releaseBackup
6+
pom.xml.versionsBackup
7+
pom.xml.next
8+
release.properties
9+
dependency-reduced-pom.xml
10+
buildNumber.properties
11+
.mvn/timing.properties
12+
.mvn/wrapper/maven-wrapper.properties
13+
14+
# IDE
15+
.idea/
16+
*.iml
17+
*.iws
18+
*.ipr
19+
.vscode/
20+
21+
# Build
22+
*.class
23+
*.log
24+
*.ctxt
25+
*.jar
26+
*.war
27+
*.ear
28+
*.zip
29+
*.tar.gz
30+
*.rar
31+
32+
# Project specific
33+
test.ipm
34+
test.ipm.csv
35+
MCCredit.ipm
36+
MCCredit.ipm.csv
37+
*.DS_Store

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# cardutil-java
2+
3+
**cardutil-java** is a Java port of the [cardutil](https://github.com/adelosa/cardutil) Python package for working with payment card systems, including command-line tools for working with Mastercard IPM files.
4+
5+
![Java CI](https://github.com/charisad/cardutil-java/actions/workflows/maven.yml/badge.svg)
6+
![JitPack](https://jitpack.io/v/charisad/cardutil-java.svg)
7+
8+
## Features
9+
* **ISO8583 Message Parsing**: Parse and pack ISO8583 messages.
10+
* **Mastercard IPM File Handling**: Read, write, and convert Mastercard IPM files (including 1014 blocking support).
11+
* **CLI Tools**: Convert between IPM and CSV formats.
12+
* **Cryptography Utilities**: Check digit calculator, Pin Block generator, Visa PVV calculator.
13+
* **Zero Dependencies**: Core library relies only on standard Java libraries (except for CLI which uses `commons-csv`).
14+
15+
## Installation
16+
17+
### Maven (via JitPack)
18+
Add the JitPack repository to your `pom.xml`:
19+
20+
```xml
21+
<repositories>
22+
<repository>
23+
<id>jitpack.io</id>
24+
<url>https://jitpack.io</url>
25+
</repository>
26+
</repositories>
27+
```
28+
29+
Add the dependency:
30+
31+
```xml
32+
<dependency>
33+
<groupId>com.github.charisad</groupId>
34+
<artifactId>cardutil-java</artifactId>
35+
<version>Tag</version>
36+
</dependency>
37+
```
38+
39+
## Usage
40+
41+
### Java API
42+
43+
#### ISO8583 Parsing
44+
```java
45+
import com.charisad.cardutil.Iso8583;
46+
import java.util.Map;
47+
48+
byte[] messageBytes = ...;
49+
Map<String, Object> data = Iso8583.unpack(messageBytes, null);
50+
System.out.println("MTI: " + data.get("MTI"));
51+
System.out.println("PAN: " + data.get("DE2"));
52+
```
53+
54+
#### IPM File Reading
55+
```java
56+
import com.charisad.cardutil.MciIpm;
57+
import java.nio.file.Files;
58+
import java.nio.file.Paths;
59+
60+
try (InputStream is = Files.newInputStream(Paths.get("incoming.ipm"));
61+
MciIpm.IpmReader reader = new MciIpm.IpmReader(is, true)) { // true for 1014 blocking
62+
for (Map<String, Object> record : reader) {
63+
System.out.println(record);
64+
}
65+
}
66+
```
67+
68+
### Command Line Interface (CLI)
69+
70+
The library includes a CLI for common tasks.
71+
72+
#### Convert IPM to CSV
73+
```bash
74+
mvn exec:java -Dexec.mainClass="com.charisad.cardutil.Cli" -Dexec.args="ipm2csv input.ipm -o output.csv"
75+
```
76+
77+
#### Convert CSV to IPM
78+
```bash
79+
mvn exec:java -Dexec.mainClass="com.charisad.cardutil.Cli" -Dexec.args="csv2ipm input.csv -o output.ipm"
80+
```
81+
82+
## Acknowledgements
83+
84+
This project is a direct port of the Python [cardutil](https://github.com/adelosa/cardutil) library by Anthony Delosa.
85+
* Original Author: [Anthony Delosa](https://github.com/adelosa)
86+
* Original License: MIT
87+
88+
Ported and maintained by [charisad](https://github.com/charisad).
89+
90+
## License
91+
MIT

jitpack.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
jdk:
2+
- openjdk17
3+
install:
4+
- mvn clean install -DskipTests -Dmaven.javadoc.skip=true

pom.xml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>com.charisad</groupId>
5+
<artifactId>cardutil</artifactId>
6+
<packaging>jar</packaging>
7+
<version>1.0-SNAPSHOT</version>
8+
<name>cardutil</name>
9+
<url>http://maven.apache.org</url>
10+
<properties>
11+
<maven.compiler.source>17</maven.compiler.source>
12+
<maven.compiler.target>17</maven.compiler.target>
13+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
14+
</properties>
15+
<dependencies>
16+
<dependency>
17+
<groupId>org.junit.jupiter</groupId>
18+
<artifactId>junit-jupiter-api</artifactId>
19+
<version>5.10.0</version>
20+
<scope>test</scope>
21+
</dependency>
22+
<repositories>
23+
<repository>
24+
<id>jitpack.io</id>
25+
<url>https://jitpack.io</url>
26+
</repository>
27+
</repositories>
28+
29+
<dependency>
30+
<groupId>com.github.charisad</groupId>
31+
<artifactId>cardutil</artifactId>
32+
<version>1.0-SNAPSHOT</version>
33+
</dependency>
34+
35+
<dependency>
36+
<groupId>org.junit.jupiter</groupId>
37+
<artifactId>junit-jupiter-engine</artifactId>
38+
<version>5.10.0</version>
39+
<scope>test</scope>
40+
</dependency>
41+
<!-- SLF4J for logging, binding to simple logger for zero-config defaults -->
42+
<dependency>
43+
<groupId>org.slf4j</groupId>
44+
<artifactId>slf4j-api</artifactId>
45+
<version>2.0.9</version>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.slf4j</groupId>
49+
<artifactId>slf4j-simple</artifactId>
50+
<version>2.0.9</version>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.apache.commons</groupId>
54+
<artifactId>commons-csv</artifactId>
55+
<version>1.10.0</version>
56+
</dependency>
57+
</dependencies>
58+
59+
<build>
60+
<plugins>
61+
<plugin>
62+
<groupId>org.apache.maven.plugins</groupId>
63+
<artifactId>maven-source-plugin</artifactId>
64+
<version>3.3.0</version>
65+
<executions>
66+
<execution>
67+
<id>attach-sources</id>
68+
<goals>
69+
<goal>jar</goal>
70+
</goals>
71+
</execution>
72+
</executions>
73+
</plugin>
74+
<plugin>
75+
<groupId>org.apache.maven.plugins</groupId>
76+
<artifactId>maven-javadoc-plugin</artifactId>
77+
<version>3.6.3</version>
78+
<executions>
79+
<execution>
80+
<id>attach-javadocs</id>
81+
<goals>
82+
<goal>jar</goal>
83+
</goals>
84+
</execution>
85+
</executions>
86+
</plugin>
87+
</plugins>
88+
</build>
89+
</project>
90+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.charisad.cardutil;
2+
3+
public record BitConfig(
4+
String fieldName,
5+
FieldType fieldType,
6+
int fieldLength,
7+
String fieldProcessor,
8+
String fieldProcessorConfig,
9+
String fieldJavaType,
10+
String fieldDateFormat
11+
) {
12+
public enum FieldType {
13+
FIXED, LLVAR, LLLVAR
14+
}
15+
16+
public static Builder builder() {
17+
return new Builder();
18+
}
19+
20+
public static class Builder {
21+
private String fieldName;
22+
private FieldType fieldType;
23+
private int fieldLength;
24+
private String fieldProcessor;
25+
private String fieldProcessorConfig;
26+
private String fieldJavaType;
27+
private String fieldDateFormat;
28+
29+
public Builder fieldName(String fieldName) { this.fieldName = fieldName; return this; }
30+
public Builder fieldType(FieldType fieldType) { this.fieldType = fieldType; return this; }
31+
public Builder fieldLength(int fieldLength) { this.fieldLength = fieldLength; return this; }
32+
public Builder fieldProcessor(String fieldProcessor) { this.fieldProcessor = fieldProcessor; return this; }
33+
public Builder fieldProcessorConfig(String fieldProcessorConfig) { this.fieldProcessorConfig = fieldProcessorConfig; return this; }
34+
public Builder fieldJavaType(String fieldJavaType) { this.fieldJavaType = fieldJavaType; return this; }
35+
public Builder fieldDateFormat(String fieldDateFormat) { this.fieldDateFormat = fieldDateFormat; return this; }
36+
37+
public BitConfig build() {
38+
return new BitConfig(fieldName, fieldType, fieldLength, fieldProcessor, fieldProcessorConfig, fieldJavaType, fieldDateFormat);
39+
}
40+
}
41+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.charisad.cardutil;
2+
3+
import java.util.BitSet;
4+
5+
public class BitUtils {
6+
7+
/**
8+
* Converts a byte array to a BitSet.
9+
* The first bit of the byte array (byte 0, bit 7) becomes bit 0 of the BitSet.
10+
* Note: BitSet grows automatically.
11+
*/
12+
public static BitSet fromBytes(byte[] bytes) {
13+
BitSet bits = new BitSet(bytes.length * 8);
14+
for (int i = 0; i < bytes.length * 8; i++) {
15+
if ((bytes[i / 8] & (1 << (7 - (i % 8)))) > 0) {
16+
bits.set(i);
17+
}
18+
}
19+
return bits;
20+
}
21+
22+
/**
23+
* Converts a BitSet to a byte array.
24+
* @param bits The BitSet to convert.
25+
* @param lengthBytes The expected length of the byte array (e.g., 8 bytes for 64 bits, 16 bytes for 128 bits).
26+
* If the BitSet has fewer bits, the result is padded with zeros.
27+
*/
28+
public static byte[] toBytes(BitSet bits, int lengthBytes) {
29+
byte[] bytes = new byte[lengthBytes];
30+
for (int i = 0; i < lengthBytes * 8; i++) {
31+
if (bits.get(i)) {
32+
bytes[i / 8] |= (1 << (7 - (i % 8)));
33+
}
34+
}
35+
return bytes;
36+
}
37+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.charisad.cardutil;
2+
3+
public class Card {
4+
5+
/**
6+
* Calculate Luhn 10 check digit.
7+
* @param cardNumber Number excluding check digit.
8+
* @return Check digit.
9+
*/
10+
public static String calculateCheckDigit(String cardNumber) {
11+
// Python: digits = [int(digit) for digit in card_number if digit.isdigit()]
12+
// total = sum([sum(divmod(multiplier * digit, 10)) for digit, multiplier in zip(digits[::-1], cycle([2, 1]))])
13+
// return str((total * 9) % 10)
14+
15+
int sum = 0;
16+
boolean alternate = true;
17+
for (int i = cardNumber.length() - 1; i >= 0; i--) {
18+
int n = Character.getNumericValue(cardNumber.charAt(i));
19+
if (alternate) {
20+
n *= 2;
21+
if (n > 9) {
22+
n = (n % 10) + 1;
23+
}
24+
}
25+
sum += n;
26+
alternate = !alternate;
27+
}
28+
return String.valueOf((sum * 9) % 10);
29+
}
30+
31+
public static boolean validateCheckDigit(String cardNumber) {
32+
if (cardNumber == null || cardNumber.length() < 2) return false;
33+
String number = cardNumber.substring(0, cardNumber.length() - 1);
34+
String checkDigit = cardNumber.substring(cardNumber.length() - 1);
35+
return calculateCheckDigit(number).equals(checkDigit);
36+
}
37+
38+
public static String addCheckDigit(String cardNumber) {
39+
return cardNumber + calculateCheckDigit(cardNumber);
40+
}
41+
42+
public static String mask(String cardNumber) {
43+
return mask(cardNumber, '*');
44+
}
45+
46+
public static String mask(String cardNumber, char maskChar) {
47+
if (cardNumber == null || cardNumber.length() <= 10) return cardNumber;
48+
// First 6, last 4
49+
StringBuilder sb = new StringBuilder();
50+
sb.append(cardNumber.substring(0, 6));
51+
for (int i = 0; i < cardNumber.length() - 10; i++) {
52+
sb.append(maskChar);
53+
}
54+
sb.append(cardNumber.substring(cardNumber.length() - 4));
55+
return sb.toString();
56+
}
57+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.charisad.cardutil;
2+
3+
public class CardutilError extends RuntimeException {
4+
public CardutilError(String message) {
5+
super(message);
6+
}
7+
8+
public CardutilError(String message, Throwable cause) {
9+
super(message, cause);
10+
}
11+
}

0 commit comments

Comments
 (0)