Skip to content

Commit 5e2caa7

Browse files
fix(#74): implement ISO 7064 Mod 37-2 check character for ISBT 128
Add proper check character computation per ISO/IEC 7064. The DIN now includes a Mod 37-2 check character (0-9, A-Z, or *) as the final character. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent aff7ac1 commit 5e2caa7

3 files changed

Lines changed: 65 additions & 5 deletions

File tree

src/encoders/isbt128.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,38 @@
88

99
import { InvalidInputError } from "../errors";
1010

11+
/**
12+
* ISO/IEC 7064 Mod 37-2 check character set: 0-9 (values 0-9), A-Z (values 10-35), * (value 36)
13+
*/
14+
const MOD37_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*";
15+
16+
/**
17+
* Compute ISO/IEC 7064 Modulo 37-2 check character
18+
*
19+
* Algorithm (pure system, radix 2, modulus 37):
20+
* 1. Start with sum = 0
21+
* 2. For each character: sum = ((sum + charValue) * 2) mod 37
22+
* 3. Check value = (37 - sum) mod 37
23+
* 4. Map back to character set
24+
*
25+
* @param data - Input string containing only characters from the Mod 37-2 character set
26+
* @returns Single check character
27+
*/
28+
export function iso7064Mod37_2(data: string): string {
29+
let sum = 0;
30+
for (let i = 0; i < data.length; i++) {
31+
const idx = MOD37_CHARSET.indexOf(data[i]);
32+
if (idx === -1) {
33+
throw new InvalidInputError(
34+
`Invalid character '${data[i]}' for ISO 7064 Mod 37-2 check character computation`,
35+
);
36+
}
37+
sum = ((sum + idx) * 2) % 37;
38+
}
39+
const check = (37 - sum) % 37;
40+
return MOD37_CHARSET[check];
41+
}
42+
1143
/**
1244
* Encode ISBT 128 Donation Identification Number (DIN)
1345
* Format: =FLLLLLYYYNNNNNN where:
@@ -48,7 +80,9 @@ export function encodeISBT128DIN(
4880

4981
const facility = facilityNumber.padStart(5, "0");
5082
const donation = donationNumber.padStart(6, "0");
51-
return `=${countryCode}${facility}${year}${donation}`;
83+
const din = `${countryCode}${facility}${year}${donation}`;
84+
const check = iso7064Mod37_2(din);
85+
return `=${din}${check}`;
5286
}
5387

5488
/**

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export {
112112
encodeISBT128Component,
113113
encodeISBT128Expiry,
114114
encodeISBT128BloodGroup,
115+
iso7064Mod37_2,
115116
} from "./encoders/isbt128";
116117

117118
// --- Renderers ---

test/encoders-isbt128.test.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import {
44
encodeISBT128Component,
55
encodeISBT128Expiry,
66
encodeISBT128BloodGroup,
7+
iso7064Mod37_2,
78
} from "../src/encoders/isbt128";
89
import { barcode } from "../src/index";
910

1011
describe("ISBT 128 DIN", () => {
11-
it("formats donation identification number", () => {
12+
it("formats donation identification number with check character", () => {
1213
const result = encodeISBT128DIN("US", "12345", "26", "000001");
13-
expect(result).toBe("=US1234526000001");
14+
expect(result).toBe("=US12345260000019");
1415
});
1516

16-
it("pads facility and donation numbers", () => {
17+
it("pads facility and donation numbers with check character", () => {
1718
const result = encodeISBT128DIN("GB", "1", "26", "1");
18-
expect(result).toBe("=GB0000126000001");
19+
expect(result).toBe("=GB0000126000001O");
1920
});
2021

2122
it("throws on invalid country code", () => {
@@ -62,3 +63,27 @@ describe("ISBT 128 Blood Group", () => {
6263
expect(() => encodeISBT128BloodGroup("")).toThrow();
6364
});
6465
});
66+
67+
describe("ISO 7064 Mod 37-2 check character", () => {
68+
it("computes correct check for numeric input", () => {
69+
expect(iso7064Mod37_2("0000000000")).toBe("0");
70+
});
71+
72+
it("computes correct check for alphanumeric DIN data", () => {
73+
expect(iso7064Mod37_2("US1234526000001")).toBe("9");
74+
});
75+
76+
it("computes correct check for another DIN", () => {
77+
expect(iso7064Mod37_2("GB0000126000001")).toBe("O");
78+
});
79+
80+
it("throws on invalid character", () => {
81+
expect(() => iso7064Mod37_2("us12345")).toThrow();
82+
});
83+
84+
it("handles asterisk in character set", () => {
85+
// * has value 36 in the Mod 37-2 character set
86+
const result = iso7064Mod37_2("*");
87+
expect(result).toMatch(/^[0-9A-Z*]$/);
88+
});
89+
});

0 commit comments

Comments
 (0)