Hagemeister Conception

E-Rechnungen in FileMaker validieren und visualisieren

Praxisanleitung zur API-Anbindung, Fehlerbehandlung, SVRL-Auswertung und PDF-Visualisierung.

Der Validierungsdienst speichert keine Rechnungsdaten; sie werden ausschließlich im flüchtigen RAM-Arbeitsspeicher verarbeitet. Die Nicht-Speicherung der Rechnungsdaten wird vertraglich zugesichert.

Anleitung: E-Rechnungen in FileMaker validieren und visualisieren

Originalrechnung → Format erkennen → validieren → visualisieren → strukturierte Daten übernehmen

Mit dem folgenden Muster senden Sie eine XML- oder PDF/A-Rechnung aus FileMaker an den Validierungsdienst, werten die Antwort aus und speichern die erzeugte Rechnungsansicht als PDF in einem Containerfeld. Passen Sie die Feld- und Tabellennamen an Ihre eigene Lösung an.

Alle Codebeispiele verwenden englische FileMaker-Scriptschritte und -Optionen. Tabellenauftreten, Feldnamen und sichtbare Dialogtexte bleiben lösungsspezifisch.

Wichtig: Den persönlichen API-Endpunkt und gegebenenfalls einen API-Schlüssel erhalten Sie separat. Ersetzen Sie <API-ENDPUNKT> durch die mitgeteilte Adresse und veröffentlichen Sie weder Endpunkt noch Zugangsdaten.

1. Voraussetzungen

Empfohlen werden folgende Felder:

FeldTypVerwendung
Rechnung::OriginaldateiContainerXML- oder PDF-Eingabedatei
Rechnung::VisualisierungContainerzurückgeliefertes PDF
Rechnung::API_AntwortTextvollständige JSON-Antwort für Diagnose und Nachvollziehbarkeit
Rechnung::ValidierungenTextListe der ausgeführten Prüfungen
Rechnung::PruefberichteTextSVRL-Berichte oder daraus abgeleitete Meldungen
Rechnung::StrukturierteDatenTextoptionaler Inhalt aus content
Rechnung::ValidierungsstatusTexteigener zusammengefasster Status

Bewahren Sie den API-Schlüssel in einer geschützten Einstellungstabelle oder einer geeigneten Geheimnisverwaltung auf. Er gehört nicht in ein ungeschütztes globales Feld, einen Script-Kommentar oder ein Fehlerprotokoll.

2. Aufbau der API-Response

Eine erfolgreiche Antwort kann beispielsweise so aufgebaut sein:

{
  "validation": [
    "<AUSGEFUEHRTES-XSD>",
    "<AUSGEFUEHRTE-REGELPRUEFUNG>"
  ],
  "reports": [
    "<svrl:schematron-output>...</svrl:schematron-output>"
  ],
  "format": "factur-x",
  "profile": "en16931",
  "pdf": "<BASE64-KODIERTES-PDF>",
  "content": {}
}

Nicht jedes Feld ist in jeder Antwort vorhanden:

ElementDatentypBedeutung
validation Array Namen der technisch ausgeführten XSD- und Regelprüfungen. Ein Schematron-Name bestätigt die Ausführung, aber noch nicht die fachliche Fehlerfreiheit.
reports Array XML-Prüfberichte als Strings im SVRL-Format. svrl:failed-assert kennzeichnet eine verletzte Regel; location, id, flag und svrl:text beschreiben Fundstelle, Regel, Schweregrad und Meldung.
format Text Erkanntes Rechnungsformat, soweit es eindeutig bestimmt werden konnte, beispielsweise factur-x.
profile Text Erkanntes Factur-X-Profil, beispielsweise minimum, basicwl, basic, en16931 oder extended.
pdf Text Erzeugte PDF-Visualisierung als Base64-kodierter Text. Das Feld kann fehlen, wenn das Format nicht visualisiert wurde oder eine Transformation fehlgeschlagen ist.
content Objekt Aus der Rechnung abgeleitete strukturierte Daten. Aufbau und Pfade hängen vom Format ab; die Daten ersetzen nicht die Originalrechnung.
detail Text oder Objekt Fehlerdetail, insbesondere bei HTTP-Fehlern wie 400, 403 oder 500.
Message Text Allgemeine Verarbeitungsmeldung des Dienstes.
error Text oder Objekt Fehlertext oder umfangreiche Fehlerstruktur. Speichern Sie im Fehlerfall deshalb immer die vollständige Antwort.
Wichtig: HTTP-Status 200 bedeutet nur, dass der Dienst die Anfrage beantworten konnte. Ob die Rechnung fachlich gültig ist, ergibt sich erst aus der erwarteten XSD-Prüfung in validation und der Auswertung der SVRL-Berichte in reports. Ein leeres reports-Array bedeutet allein weder „gültig“ noch „ungültig“.

Verwenden Sie format und profile nur für Folgeentscheidungen, wenn die Elemente tatsächlich vorhanden sind. UBL Invoice, UBL CreditNote und CII-basierte Formate können in content unterschiedliche Strukturen und Pfade zurückgeben.

3. FileMaker-Script einrichten

1Eingabe prüfen

Set Error Capture [ On ]
Allow User Abort [ Off ]

If [ IsEmpty ( Rechnung::Originaldatei ) ]
    Show Custom Dialog [ "Keine Rechnung" ;
        "Bitte wählen Sie zuerst eine XML- oder PDF-Datei aus." ]
    Exit Script [ Text Result: "Eingabedatei fehlt" ]
End If

2Request erzeugen

Set Variable [ $request ; Value:
    JSONSetElement (
        "{}" ;
        [ "data" ;
          Base64EncodeRFC ( 4648 ; Rechnung::Originaldatei ) ;
          JSONString ]
    )
]

Set Variable [ $url ; Value: "<API-ENDPUNKT>" ]
Set Variable [ $apiKey ; Value: Einstellungen::API_Key ]

Base64EncodeRFC ( 4648 ; … ) erzeugt Base64 ohne zusätzliche Zeilenumbrüche. Ein Container mit der unveränderten Originaldatei verhindert unbemerkte Änderungen an Zeichensatz oder Zeilenenden.

3cURL-Optionen aufbauen

Set Variable [ $curlOptions ; Value:
    "--request POST" & ¶ &
    "--header " & Quote ( "Content-Type: application/json" ) & ¶ &
    If (
        not IsEmpty ( $apiKey ) ;
        "--header " & Quote ( "x-api-key: " & $apiKey ) & ¶ ;
        ""
    ) &
    "--data @$request" & ¶ &
    "--dump-header $responseHeaders" & ¶ &
    "--connect-timeout 15" & ¶ &
    "--max-time 120" & ¶ &
    "--show-error"
]

Senden Sie den Header x-api-key nur, wenn tatsächlich ein Schlüssel vorhanden ist. --fail wird nicht verwendet, damit auch bei HTTP 400, 403 oder 500 der JSON-Fehlertext des Servers in FileMaker ankommt.

4Anfrage senden

Set Variable [ $response ; Value: "" ]
Set Variable [ $responseHeaders ; Value: "" ]

Insert from URL [
    Select ;
    With dialog: Off ;
    Target: $response ;
    $url ;
    Verify SSL Certificates ;
    cURL options: $curlOptions
]

Set Variable [ $lastError ; Value:
    JSONSetElement ( "{}" ;
        [ "code" ; Get ( LastError ) ; JSONNumber ] ;
        [ "detail" ; Get ( LastErrorDetail ) ; JSONString ]
    )
]
Set Variable [ $fmError ; Value: JSONGetElement ( $lastError ; "code" ) ]
Set Variable [ $fmErrorDetail ; Value: JSONGetElement ( $lastError ; "detail" ) ]

Set Field [ Rechnung::API_Antwort ; $response ]

Beide Fehlerwerte werden innerhalb desselben ersten Set Variable-Schritts nach Insert from URL ausgewertet. Aktivieren Sie produktiv immer Verify SSL Certificates und verwenden Sie nicht --insecure.

5HTTP-Status und Transportfehler behandeln

Set Variable [ $headerLines ; Value:
    Substitute ( $responseHeaders ; Char ( 13 ) ; "" )
]
Set Variable [ $headerIndex ; Value: ValueCount ( $headerLines ) ]
Set Variable [ $httpStatusLine ; Value: "" ]

Loop [ Flush: Always ]
    Exit Loop If [ $headerIndex < 1 ]
    Set Variable [ $httpStatusLine ; Value:
        GetValue ( $headerLines ; $headerIndex )
    ]
    Exit Loop If [ Left ( $httpStatusLine ; 5 ) = "HTTP/" ]
    Set Variable [ $headerIndex ; Value: $headerIndex - 1 ]
End Loop

Set Variable [ $httpStatus ; Value:
    If ( Left ( $httpStatusLine ; 5 ) = "HTTP/" ;
        GetAsNumber ( MiddleWords ( $httpStatusLine ; 2 ; 1 ) ) ; 0 )
]

If [ $fmError ≠ 0 and $httpStatus = 0 ]
    Set Variable [ $result ; Value:
        JSONSetElement ( "{}" ;
            [ "status" ; "transport_error" ; JSONString ] ;
            [ "filemaker_error" ; $fmError ; JSONNumber ] ;
            [ "detail" ; $fmErrorDetail ; JSONString ]
        )
    ]
    Exit Script [ Text Result: $result ]
End If

Bei Weiterleitungen können mehrere Statuszeilen vorkommen. Maßgeblich ist die letzte mit HTTP/ beginnende Zeile. Status 0 bedeutet, dass keine HTTP-Antwort ausgewertet werden konnte.

6JSON-Antwort prüfen

Set Variable [ $formatted ; Value: JSONFormatElements ( $response ) ]

If [ Left ( $formatted ; 1 ) = "?" ]
    Exit Script [ Text Result:
        JSONSetElement ( "{}" ;
            [ "status" ; "invalid_response" ; JSONString ] ;
            [ "http_status" ; $httpStatus ; JSONNumber ] ;
            [ "response" ; $response ; JSONString ]
        )
    ]
End If

If [ JSONGetElementType ( $response ; "" ) ≠ JSONObject ]
    Exit Script [ Text Result: "Die API-Antwort ist kein JSON-Objekt." ]
End If

Set Variable [ $responseKeys ; Value: JSONListKeys ( $response ; "" ) ]
Set Variable [ $detail ; Value:
    If ( not IsEmpty ( FilterValues ( $responseKeys ; "detail" ) ) ;
        JSONGetElement ( $response ; "detail" ) ; "" )
]
Set Variable [ $message ; Value:
    If ( not IsEmpty ( FilterValues ( $responseKeys ; "Message" ) ) ;
        JSONGetElement ( $response ; "Message" ) ; "" )
]
Set Variable [ $error ; Value:
    If ( not IsEmpty ( FilterValues ( $responseKeys ; "error" ) ) ;
        JSONGetElement ( $response ; "error" ) ; "" )
]

If [ $httpStatus < 200 or $httpStatus ≥ 300 ]
    Exit Script [ Text Result:
        JSONSetElement ( "{}" ;
            [ "status" ; "http_error" ; JSONString ] ;
            [ "http_status" ; $httpStatus ; JSONNumber ] ;
            [ "detail" ; $detail ; JSONString ] ;
            [ "message" ; $message ; JSONString ] ;
            [ "error" ; $error ; JSONString ]
        )
    ]
End If

Prüfen Sie in dieser Reihenfolge: FileMaker-/cURL-Fehler, HTTP-Status aus $responseHeaders, gültiges JSON, die API-Felder detail, Message und error sowie zuletzt die fachliche Validierung.

7Validierungen übernehmen

Set Variable [ $validations ; Value:
    If ( not IsEmpty ( FilterValues ( $responseKeys ; "validation" ) ) ;
        JSONListValues ( $response ; "validation" ) ; "" )
]

Set Field [ Rechnung::Validierungen ; $validations ]

Ein Schematron-Name bestätigt nur, dass die Regelprüfung ausgeführt wurde. Ob fachliche Fehler vorliegen, ergibt sich aus den SVRL-Berichten in reports.

8SVRL-Prüfberichte zählen und speichern

Set Variable [ $reportCount ; Value:
    If ( not IsEmpty ( FilterValues ( $responseKeys ; "reports" ) ) ;
        ValueCount ( JSONListKeys ( $response ; "reports" ) ) ; 0 )
]
Set Variable [ $i ; Value: 0 ]
Set Variable [ $failedAssertCount ; Value: 0 ]

Loop [ Flush: Always ]
    Exit Loop If [ $i ≥ $reportCount ]

    Set Variable [ $report ; Value:
        JSONGetElement ( $response ; "reports[" & $i & "]" )
    ]
    Set Variable [ $failedAssertCount ; Value:
        $failedAssertCount +
        PatternCount ( $report ; "<svrl:failed-assert" )
    ]

    Set Variable [ $i ; Value: $i + 1 ]
End Loop

Set Field [ Rechnung::Pruefberichte ;
    If ( $reportCount > 0 ;
        JSONFormatElements ( JSONGetElement ( $response ; "reports" ) ) ; "" )
]

Die Textzählung eignet sich für eine einfache Ja/Nein-Anzeige. Benötigen Sie Regel-ID, Schweregrad, XPath und Meldung einzeln, verarbeiten Sie das SVRL mit einem XML-Parser.

9Visualisiertes PDF speichern

Set Variable [ $pdfBase64 ; Value:
    If ( not IsEmpty ( FilterValues ( $responseKeys ; "pdf" ) ) ;
        JSONGetElement ( $response ; "pdf" ) ; "" )
]

If [ not IsEmpty ( $pdfBase64 ) ]
    Set Variable [ $pdfContainer ; Value:
        Base64Decode (
            $pdfBase64 ;
            "Rechnungsvisualisierung.pdf"
        )
    ]
    Set Field [ Rechnung::Visualisierung ; $pdfContainer ]
End If

Der Dateiname im zweiten Parameter von Base64Decode ist wichtig, damit FileMaker das Ergebnis als PDF-Containerdaten und nicht als Text behandelt.

10Strukturierte Daten übernehmen

Set Variable [ $content ; Value:
    If ( not IsEmpty ( FilterValues ( $responseKeys ; "content" ) ) ;
        JSONGetElement ( $response ; "content" ) ; "" )
]

If [ not IsEmpty ( $content ) ]
    Set Field [ Rechnung::StrukturierteDaten ;
        JSONFormatElements ( $content )
    ]
End If

content ist eine abgeleitete Darstellung und kein verlustfreier Ersatz für die Originalrechnung. Bewahren Sie die ursprüngliche XML- oder PDF-Datei auf.

4. Validierung und Gesamtstatus auswerten

Die Antwort kann validation, reports, format, profile, pdf und content enthalten. Fehler werden vor allem über detail, Message oder error gemeldet. Eine HTTP-Antwort mit Status 200 bedeutet deshalb nicht automatisch, dass die Rechnung fachlich gültig ist.

StatusBedeutung
transport_errorFileMaker konnte den Dienst nicht ordnungsgemäß erreichen.
http_errorDer Dienst antwortete beispielsweise mit 400, 403 oder 500.
processing_errorDie Antwort enthält detail, Message oder relevante Daten in error.
validation_incompleteDie erwartete XSD-Prüfung fehlt oder es gibt keinen auswertbaren Regelbericht.
validation_failedMindestens ein nicht akzeptierter failed-assert wurde gefunden.
validDie erwartete XSD-Prüfung ist vorhanden und die relevanten Berichte enthalten keine nicht akzeptierten failed-assert-Einträge.

5. Typische Fehlerbilder

BeobachtungWahrscheinliche UrsacheMaßnahme
FileMaker-Fehler 1631Verbindung, DNS, TLS oder TimeoutExterne Fehlerdetails, Zertifikat und Erreichbarkeit prüfen
HTTP 400Request leer oder kein gültiges JSON$request mit JSONFormatElements prüfen
HTTP 403Mitgesendeter API-Schlüssel ungültigSchlüssel und Header prüfen; leeren Header nicht mitsenden
HTTP 500Unerwarteter ServerfehlerVollständige Antwort und Zeitpunkt für den Support sichern
failed-assert im ReportSchema technisch bestanden, Geschäftsregel verletztRegel-ID, XPath und Meldung auswerten
pdf fehltFormat nicht visualisierbar oder Transformation fehlgeschlagenTransformationsdetails und vollständige Antwort prüfen
PDF im Container nicht verwendbarBase64 als Text statt als Container dekodiertBase64Decode mit Dateiname und .pdf verwenden

6. Wichtige Hinweise

  • Bei unterstützten hybriden Factur-X/ZUGFeRD-Rechnungen muss die strukturierte Rechnung normgerecht als eingebettete XML-Datei vorhanden sein.
  • Verwenden Sie ausschließlich HTTPS und aktivieren Sie die SSL-Zertifikatsprüfung.
  • Protokollieren Sie keine API-Schlüssel.
  • Begrenzen Sie die Laufzeit mit --connect-timeout und --max-time.
  • Speichern Sie Originaldatei, vollständige Antwort, Zeitpunkt und Gesamtstatus für die Fehleranalyse.
  • Prüfen Sie bei Server-Scripts, ob ausgehende HTTPS-Verbindungen erlaubt sind.

7. Abnahmetest

Testen Sie die Integration mindestens mit diesen Fällen:

  1. gültige XML-Rechnung,
  2. fachlich fehlerhafte, aber technisch lesbare XML-Rechnung,
  3. ungültige oder beschädigte XML-Datei,
  4. PDF/A-Rechnung mit und ohne eingebettete factur-x.xml,
  5. leerer Request sowie korrekter und ungültiger API-Schlüssel,
  6. nicht erreichbarer Endpunkt oder erzwungener Timeout,
  7. Antwort mit und ohne PDF und
  8. Ausführung in FileMaker Pro sowie gegebenenfalls als FileMaker-Server-Script.

Der Test ist bestanden, wenn die Lösung Transportfehler, HTTP-Fehler, fachliche Fehler und eine gültige Rechnung sichtbar voneinander unterscheidet.

Technische Referenz

← Zurück zum E-Rechnung Visualisierer