Skip to content

Check the IBAN and the VAT ID

The script recomputes the check digits of an incoming invoice’s IBAN and checks that the VAT ID has the right form. It doesn’t check whether the VAT ID is actually assigned. On an error the invoice goes back to the person who uploaded it to be corrected, with a hint, otherwise straight to the approval. With a card it needs no script: Check the mandatory details checks the IBAN’s check digits and the VAT ID’s form, among others.

The workflow starts for every document of the type incoming-invoice. Its file workflow.yaml:

$schema: 'https://docs.acriva.de/schemas/workflow.schema.json'
format: 1
schemaVersion: 4
sdk: '1.0.0'
key: iban-vat-id-check
name: IBAN und USt-IdNr. prüfen
template: custom
trigger:
documentType: incoming-invoice
fields:
- key: iban
kind: text
labelDe: IBAN
labelEn: IBAN
documentTypes:
- incoming-invoice
- key: vatId
kind: text
labelDe: USt-IdNr.
labelEn: VAT ID
documentTypes:
- incoming-invoice
start: check
steps:
- kind: script
name: check
label: IBAN und USt-IdNr. prüfen
script: check.ts
hook: onDocumentArrived
inputs:
- kind: field
key: iban
type: text
- kind: field
key: vatId
type: text
returns:
- key: result
type: enumeration
values:
- valid
- invalid
branch: result
exits:
- value: valid
next: approve
- value: invalid
next: fix
next: approve
- kind: humanTask
name: fix
label: Bankdaten korrigieren
taskKind: fix
assignees:
- when: null
assignee:
kind: role
role: Buchhaltung
fallback:
kind: role
role: Buchhaltung
options:
excludeRequester: false
excludeActorsOfSteps: []
pickFile: false
next: approve
- kind: humanTask
name: approve
label: Freigabe
taskKind: approve
assignees:
- when: null
assignee:
kind: costCenterResponsible
fallback:
kind: role
role: Buchhaltung
options:
excludeRequester: true
excludeActorsOfSteps: []
pickFile: false
next: approved
onRejected: rejected
- kind: end
name: approved
label: Freigegeben
outcome: approved
- kind: end
name: rejected
label: Zurückgewiesen
outcome: rejected

Hook: onDocumentArrived · Inputs: iban, vatId · Return values: result (valid, invalid)

import { defineScript } from '@acriva/workflow-sdk';
/** Removes spaces and writes letters in capitals, as IBANs and VAT IDs are compared. */
const compact = (value: string) => value.replace(/\s+/g, '').toUpperCase();
/** Whether a text has the form of an IBAN: a country code, two check digits and 11 to 30 letters or digits; 22 characters in Germany. */
function ibanFormFits(iban: string): boolean {
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(iban)) return false;
return !iban.startsWith('DE') || iban.length === 22;
}
/**
* Whether an IBAN's two check digits fit (ISO 13616, mod 97): the first four characters move to the end, every letter
* becomes a number (A = 10 … Z = 35), and the whole number leaves the remainder 1 when divided by 97.
*/
function ibanCheckDigitsFit(iban: string): boolean {
const rearranged = iban.slice(4) + iban.slice(0, 4);
let remainder = 0;
// A character at a time, so the number never grows too large; an IBAN has at most 34 characters.
for (const char of rearranged) {
const value = parseInt(char, 36);
remainder = (remainder * (value > 9 ? 100 : 10) + value) % 97;
}
return remainder === 1;
}
/**
* Whether a VAT ID has the form of one: a German one is DE and 9 digits, any other a country code and 2 to 12
* characters. It checks the form only, not whether the number is assigned: that only the Federal Central Tax Office
* (BZSt) can confirm.
*/
function vatIdFormFits(vatId: string): boolean {
if (vatId.startsWith('DE')) return /^DE\d{9}$/.test(vatId);
return /^[A-Z]{2}[A-Z0-9]{2,12}$/.test(vatId);
}
export default defineScript({
hook: 'onDocumentArrived',
outputs: { result: ['valid', 'invalid'] },
run(ctx) {
const { iban, vatId } = ctx.document.fields;
const problems: string[] = [];
// An empty field has nothing to check: not every invoice names an IBAN or a VAT ID.
if (iban != null && !ibanFormFits(compact(iban))) {
problems.push(`Die IBAN ${iban} hat nicht die Form einer IBAN (in Deutschland 22 Zeichen).`);
} else if (iban != null && !ibanCheckDigitsFit(compact(iban))) {
problems.push(`Die IBAN ${iban} hat keine gültige Prüfziffer.`);
}
if (vatId != null && !vatIdFormFits(compact(vatId))) {
problems.push(
`Die USt-IdNr. ${vatId} hat nicht die Form einer USt-IdNr. (in Deutschland DE und 9 Ziffern).`,
);
}
if (problems.length === 0) return ctx.branch('valid');
ctx.hint(`${problems.join(' ')} Bitte korrigieren Sie die Angabe anhand der Rechnung.`);
return ctx.branch('invalid');
},
});

A test runs each of these cases in the sandbox and compares the result.

  • Input: iban DE89 3704 0044 0532 0130 00, vatId DE123456789
  • Exit: valid
  • Changes: none
  • Input: iban DE89 3704 0044 0532 0130 01, vatId DE123456789
  • Exit: invalid
  • Changes: hint on the next task: “Die IBAN DE89 3704 0044 0532 0130 01 hat keine gültige Prüfziffer. Bitte korrigieren Sie die Angabe anhand der Rechnung.”
  • Input: iban null, vatId DE12345678
  • Exit: invalid
  • Changes: hint on the next task: “Die USt-IdNr. DE12345678 hat nicht die Form einer USt-IdNr. (in Deutschland DE und 9 Ziffern). Bitte korrigieren Sie die Angabe anhand der Rechnung.”