SEPA XML Generator

November 27, 2025 ยท View on GitHub

.. image:: https://travis-ci.org/raphaelm/python-sepaxml.svg?branch=master :target: https://travis-ci.org/raphaelm/python-sepaxml

.. image:: https://codecov.io/gh/raphaelm/python-sepaxml/branch/master/graph/badge.svg :target: https://codecov.io/gh/raphaelm/python-sepaxml

.. image:: http://img.shields.io/pypi/v/sepaxml.svg :target: https://pypi.python.org/pypi/sepaxml

This is a python implementation to generate SEPA XML files.

Limitations

Supported standards:

  • SEPA PAIN.001.001.03
  • SEPA PAIN.001.001.09
  • SEPA PAIN.001.001.10
  • SEPA PAIN.001.001.11
  • SEPA PAIN.008.001.02
  • SEPA PAIN.008.001.08
  • SEPA PAIN.008.001.09
  • SEPA PAIN.008.001.10

Usage

Direct debit """"""""""""

Example:

.. code:: python

from sepaxml import SepaDD
import datetime, uuid

config = {
    # "msg_id": "", # If your bank require a specific format for MsgId, you can optionnaly set it up here. Default will be randomly generated with timestamp followed by a random value.
    "name": "Test von Testenstein",
    "IBAN": "NL50BANK1234567890",
    "BIC": "BANKNL2A",
    "batch": True,
    "creditor_id": "DE26ZZZ00000000000",  # supplied by your bank or financial authority
    "currency": "EUR",  # ISO 4217
    # "instrument": "B2B",  # - default is CORE (B2C)
    "address": {
        # The address and all of its fields are optional but in some countries they are required
        "address_type": "ADDR",  # valid: ADDR, PBOX, HOME, BIZZ, MLTO, DLVY
        "department": "Head Office",
        "subdepartment": None,
        "street_name": "Musterstr.",
        "building_number": "1",
        "postcode": "12345",
        "town": "Berlin",
        "country": "DE",
        "country_subdivision": None,
        "lines": ["Line 1", "Line 2"],
    },
    # "initiating_party": "John Doe", # optional name of the initiator of the payment, required by some banks. default to ''name'
    # "initiating_party_id": "DE26ZZZ00000000002", # optional, supplied by your bank or financial authority. default to 'creditor_id'
    "ultimate_creditor": {
        # The ultimate_creditor and all of its fields are optional but in some financial institution they are required
        "name": "Real Creditor",
        "BIC_or_BEI": "REALNL2A",
        "id": "12345678900001", # can be a local official id or the creditor_id
        "id_scheme_name": "SIRET", # proprietary scheme of the id provided (i.e. SEPA, SIRET...)
    },
}
sepa = SepaDD(config, schema="pain.008.001.02", clean=True)

payment = {
    "name": "Test von Testenstein",
    "IBAN": "NL50BANK1234567890",
    "BIC": "BANKNL2A",
    "amount": 5000,  # in cents
    "type": "RCUR",  # FRST,RCUR,OOFF,FNAL
    "collection_date": datetime.date.today(),
    "mandate_id": "1234",
    "mandate_date": datetime.date.today(),
    "description": "Test transaction",
    # "endtoend_id": str(uuid.uuid1()).replace("-", ""),  # autogenerated if obmitted
    "address": {
        # The address and all of its fields are optional but in some countries they are required
        "address_type": "ADDR",  # valid: ADDR, PBOX, HOME, BIZZ, MLTO, DLVY
        "department": "Head Office",
        "subdepartment": None,
        "street_name": "Musterstr.",
        "building_number": "1",
        "postcode": "12345",
        "town": "Berlin",
        "country": "DE",
        "country_subdivision": None,
        "lines": ["Line 1", "Line 2"],
    },
    # "initiating_party": "John Doe", # optional name of the initiator of the payment, required by some banks. default to ''name'
    # "initiating_party_id": "DE26ZZZ00000000002", # optional, supplied by your bank or financial authority. default to 'creditor_id'
}
sepa.add_payment(payment)

print(sepa.export(validate=True))

Credit transfer """""""""""""""

Example:

.. code:: python

from sepaxml import SepaTransfer
import datetime, uuid

config = {
    # "msg_id": "", # If your bank require a specific message_id format, you can set it up here. Default will be randomly generated with timestamp followed by a random value.
    "name": "Test von Testenstein",
    "IBAN": "NL50BANK1234567890",
    "BIC": "BANKNL2A",
    "batch": True,
    # For non-SEPA transfers, set "domestic" to True, necessary e.g. for CH/LI
    "currency": "EUR",  # ISO 4217
    "address": {
        # The address and all of its fields are optional but in some countries they are required
        "address_type": "ADDR",  # valid: ADDR, PBOX, HOME, BIZZ, MLTO, DLVY
        "department": "Head Office",
        "subdepartment": None,
        "street_name": "Musterstr.",
        "building_number": "1",
        "postcode": "12345",
        "town": "Berlin",
        "country": "DE",
        "country_subdivision": None,
        "lines": ["Line 1", "Line 2"],
    },
}
sepa = SepaTransfer(config, clean=True)

payment = {
    "name": "Test von Testenstein",
    "IBAN": "NL50BANK1234567890",
    "BIC": "BANKNL2A",
    "amount": 5000,  # in cents
    "execution_date": datetime.date.today() + datetime.timedelta(days=2),
    "description": "Test transaction",
    # "endtoend_id": str(uuid.uuid1()).replace("-", ""),  # optional
    "address": {
        # The address and all of its fields are optional but in some countries they are required
        "address_type": "ADDR",  # valid: ADDR, PBOX, HOME, BIZZ, MLTO, DLVY
        "department": "Head Office",
        "subdepartment": None,
        "street_name": "Musterstr.",
        "building_number": "1",
        "postcode": "12345",
        "town": "Berlin",
        "country": "DE",
        "country_subdivision": None,
        "lines": ["Line 1", "Line 2"],
    },
}
sepa.add_payment(payment)

print(sepa.export(validate=True))

Development

To run the included tests::

pip install -r requirements_dev.txt
py.test tests

To automatically sort your Imports as required by CI::

pip install isort
isort -rc .

Security

If you discover a security issue, please contact us at security@pretix.eu and see our Responsible Disclosure Policy_ further information.

Credits and License

Maintainer: Raphael Michel mail@raphaelmichel.de

This basically started as a properly packaged, python 3 tested version of the PySepaDD_ implementation that was released by The Congressus under the MIT license. Thanks for your work!

The source code is released under MIT license.

Not part of the MIT-licensed project are the XML schemas in the sepaxml/schemas/ folder which are copyrighted by the ISO 20022 organization but allowed to be reproduced_ freely.

.. _PySepaDD: https://github.com/congressus/PySepaDD .. _allowed to be reproduced: https://www.iso20022.org/terms-use .. _Responsible Disclosure Policy: https://docs.pretix.eu/trust/security/disclosure/