#!/usr/bin/env bash # cURL request; Python 3 decodes and checks the returned XML without jq. set -euo pipefail if [ "$#" != 2 ]; then echo 'Usage: bash generate.sh invoice.json invoice.xml' >&2; exit 2; fi if [ "$(wc -c < "$1")" -gt 5242880 ]; then echo "Input exceeds 5 MiB" >&2; exit 1; fi response_file=$(mktemp) trap 'rm -f "$response_file"' EXIT args=(--fail-with-body --silent --show-error --max-time 30 --max-filesize 12582912 -H 'Content-Type: application/json' -H 'Accept: application/json') if [ -n "${IRONFANG_API_KEY:-}" ]; then request_key=${FINANCEWOLF_IDEMPOTENCY_KEY:-$(python3 -c 'import uuid; print(uuid.uuid4())')} args+=(-H "Authorization: Bearer $IRONFANG_API_KEY" -H "Idempotency-Key: $request_key") fi api_base=${FINANCEWOLF_API_BASE:-https://api.ironfang.uk/financewolf} if ! curl "${args[@]}" --data-binary "@$1" "${api_base%/}/v1/einvoices/generate?ruleset=latest&profile=peppol-bis-billing-3" -o "$response_file"; then cat "$response_file" >&2 exit 1 fi python3 - "$response_file" "$2" <<'PY' import base64, hashlib, json, pathlib, sys r = json.loads(pathlib.Path(sys.argv[1]).read_bytes()) v, a = r['validation'], r['artifact'] if (r['schema'] != 'financewolf/einvoice/generation-result/v1' or r['status'] != 'completed' or r['outcome'] != 'valid' or v['outcome'] != 'valid' or [l['layer'] for l in v['layers']] != ['input', 'xml', 'xsd', 'en16931', 'peppol'] or any(l['status'] != 'passed' for l in v['layers'])): sys.exit('Generation did not pass every validation layer') xml = base64.b64decode(a['data_base64'], validate=True) digest = hashlib.sha256(xml).hexdigest() if not xml or len(xml) > 5 * 1024 * 1024 or len(xml) != a['bytes'] or digest != a['sha256'] or digest != v['input']['sha256'] or len(xml) != v['input']['bytes']: sys.exit('XML does not match its validation hash/length') pathlib.Path(sys.argv[2]).write_bytes(xml) print(json.dumps({'xml': sys.argv[2], 'sha256': digest, 'ruleset': v['ruleset']['id'], 'operation_id': r.get('operation_id')})) PY