# 0011. Write IBAN accounts and the service level in .04 to .08
- **Status:** Accepted
- **Date:** 2026-11-08
- **Deciders:** Sebastien Rousseau (release 0.0.82)
## Context
The input columns `debtor_account_IBAN` and `creditor_account_IBAN` hold
IBANs. The pain.001.001.03 and .09 to .13 templates write them as
`…`. The .04 to .08 templates wrote the same value
as `…`, an account number with no scheme
name. A bank or a validator reading that cannot tell the value is an
IBAN: pain001's own SEPA checks (the `sepa-sct` profile's
`SEPA-DBTR-IBAN` and `SEPA-CDTR-IBAN`, Schematron EPC-PMT-001 and
EPC-SCT-003) found no IBAN in the bundled .05 to .08 samples, and a SEPA
bank may reject such a payment or repair it by hand.
The same templates had no `PmtTpInf` at all, so `service_level_code`
was dropped from the input, and a SEPA file built from them failed the
bundled Schematron rule EPC-PMT-002. The .03 and .09 to .13 templates
write `PmtTpInf/SvcLvl/Cd` on the payment batch when the column has a
value.
## Options considered
1. Leave .04 to .08 as they are and document it. No output change, but
five versions stay wrong and out of line with the other six.
2. Always write `` in .04 to .08. Simple, but a non-IBAN account
number put in the column (possible with `--no-identifier-checks`)
would then fail the XSD, which it passes today.
3. Write `` when the value is an IBAN and keep ``
otherwise, the switch .03 and .09 already make.
## Decision
Option 3. The templates' sandbox gains an `iban` test
(`pain001.xml._jinja.is_iban`): true for text in the XSD's IBAN shape
(`[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{1,30}`, no spaces) whose check digits pass
mod-97. The .04 to .08 templates write the debtor and creditor accounts
with `{% if … is iban %}…{% else %}…{% endif %}`; those
templates write no other account (no charges or ultimate-party account).
For the service level, the same reasoning: copy the switch .03 and .09
make. The .04 to .08 templates write
`{%- if service_level_code %}…`
after `BtchBookg`, where the XSD puts it. The .05 to .08 data preparer
used to default the value to `SEPA`, unused until now; it passes the
column as given, so the element appears only for input that sets it.
The charge bearer stays on each transaction, where these templates
already write it; the SEPA rulebook's EPC-PMT-003 now accepts it there.
## Consequences
- Breaking for output: for the same input, a valid IBAN in either column
of a .04 to .08 message moves from `` to ``, and a
non-empty `service_level_code` adds `PmtTpInf/SvcLvl/Cd` to the batch.
The golden files and bundled examples for .04 to .08 are regenerated
once for both, with `scripts/generate_golden_files.py` and
`scripts/generate_xml_examples.py`; they change only in those
elements.
- Any other account value (an account number, an IBAN written with
spaces, one with bad check digits) and input without a service level
render byte-identically to 0.0.81.
- `tests/test_account_iban_element.py` generates every version from .03
to .13 with IBAN input and requires `` for both accounts, and
`tests/test_service_level_element.py` requires the service level in
.04 to .08 and its absence without one, so the versions cannot drift
apart again.
- The bundled .05 to .08 samples pass the SEPA Schematron rulebook. The
.04 sample still does not: it uses charge bearer `SHAR` and service
level `INST` on purpose.
## Notice
The README's stability policy makes a change to generated XML breaking
and asks for it to be announced one release ahead. This one was not: it
was decided and shipped in 0.0.82. The old output was not conformant
(an IBAN with no scheme, a service level dropped), and SEPA files built
from .04 to .08 failed pain001's own checks, but that argues for a
quick announcement, not for none. The policy is unchanged. From 0.0.83
the release preflight refuses a release whose golden files changed
unless the previous release's CHANGELOG has an `### Announced` section
or the release records its exception, as this one now does.