Skip to content

A subtly wrong payments file is worse than none.

Parse, write, and validate ISO 20022 pain.001 credit transfers and pain.008 direct debits. No-float money, exact CtrlSum, IBAN mod-97, EPC charset, XSD-oracle property testing. Correctness is the whole product.

Real code from the library. Model first, XML second.

import { euros, type CreditTransferDocument } from 'sepa-xml-ts'
const doc: CreditTransferDocument = {
messageId: 'MSG-2026-0001',
createdAt: '2026-06-01T10:30:00Z',
initiatingParty: 'ACME GmbH',
batches: [
{
id: 'BATCH-001',
executionDate: '2026-06-03', // a date, never a datetime
debtor: {
name: 'ACME GmbH',
iban: 'DE89370400440532013000',
bic: 'COBADEFFXXX',
},
transfers: [
{
endToEndId: 'INV-1001',
amount: euros('123.45'),
creditor: { name: 'Beispiel AG', iban: 'NL91ABNA0417164300' },
remittanceInfo: 'Invoice 1001',
},
],
},
],
}

No-float money

Amounts are bigint minor units, never JS number arithmetic. euros("123.45") gives 12345n. A float can never sneak into your CtrlSum.

Exact CtrlSum

The control sum is derived by the writer with exact integer addition across all transfers. Zero rounding tolerance.

EPC charset enforced

SEPA character set EPC217-08 is enforced as a concern separate from XML escaping. Non-SEPA characters are rejected, not silently dropped.

IBAN mod-97 check

Every IBAN is validated by mod-97 checksum, not just a regex. Invalid IBANs are caught before a file is ever written.

Dates, not datetimes

Execution and collection dates are plain YYYY-MM-DD values. The library never stamps a timezone on a date-only field.

XSD-as-oracle testing

Every build generates hundreds of random valid models, writes them to XML, and asserts each file passes the official EPC XSD. The XSD is the ground truth.


  1. Install the package.

    Terminal window
    npm install sepa-xml-ts
    # or
    pnpm add sepa-xml-ts

    ESM-only, ships its own type declarations. Node 18+.

  2. Build a document.

    import { euros, type CreditTransferDocument } from 'sepa-xml-ts'
    const doc: CreditTransferDocument = {
    messageId: 'MSG-001',
    createdAt: new Date().toISOString(),
    initiatingParty: 'My Company GmbH',
    batches: [
    {
    id: 'BATCH-001',
    executionDate: '2026-06-10',
    debtor: {
    name: 'My Company GmbH',
    iban: 'DE89370400440532013000',
    },
    transfers: [
    {
    endToEndId: 'PAY-001',
    amount: euros('99.50'),
    creditor: { name: 'Supplier AG', iban: 'NL91ABNA0417164300' },
    },
    ],
    },
    ],
    }
  3. Write and validate.

    import { writeCreditTransfer, validate } from 'sepa-xml-ts'
    const result = validate(doc)
    if (result.ok) {
    const xml = writeCreditTransfer(result.data)
    // xml is a valid pain.001.001.09 string, ready to submit
    }


Quickstart

Build a credit transfer document, validate it, write it to XML, and parse it back in five minutes. Read the quickstart

GitHub

Source code, issues, and the test suite with XSD-oracle property tests. View on GitHub