Skip to main content

Crate sepa

Crate sepa 

Source
Expand description

§sepa — SEPA payment utilities for Rust

Provides:

  • IBAN validation (iban) — ISO 13616 mod-97 and the registered national BBAN structure, 89-country registry, SEPA membership
  • BIC validation (bic) — ISO 9362:2022, including the country code
  • Creditor Identifier validation (creditor_id) — EPC AT-02
  • Postal addresses (address) — structured and hybrid PstlAdr
  • pain.001 builder (pain001) — SCT, SCT Instant and OCT Inst
  • pain.008 builder (pain008) — SEPA Direct Debit (CORE + B2B)
  • pain.007 builder (pain007) — SEPA Direct Debit reversal
  • pain.002 parser (pain002) — Payment Status Report, incl. Verification of Payee
  • camt.055 builder (camt055) — Payment Cancellation Request (recall)
  • camt.029 parser (camt029) — Resolution of Investigation — the answer to a recall
  • camt.052 parser (camt052) — Bank-to-Customer Report (intraday)
  • camt.053 parser (camt053) — Bank-to-Customer Statement (end-of-day)
  • camt.054 parser (camt054) — Bank-to-Customer Notification, returns
  • EPC field validation (validate) — the rules the XSD does not enforce
  • SEPA character set (charset) — validation and transliteration
  • Typed dates (date) — IsoDate / IsoDateTime for every date field
  • Integer-safe money utilities (ct_to_eur_str, ct_from_eur_str)

Monetary values use integer hundredths, never f64 or an inferred minor-unit scale. Builder dates use IsoDate / IsoDateTime; readers also retain decoded raw observations.

Dependencies: thiserror and quick-xml (required); serde, time and chrono (optional features).

§Schema versions

The builders default to the versions the EPC 2023 rulebooks mandated from 19 November 2023 — pain.001.001.09 and pain.008.001.08. Which version a bank actually requires varies by bank and by regulatory cutover, so it is a per-message choice:

MessageVariants
pain.001pain.001.001.09 (default), pain.001.001.03, pain.001.003.03
pain.008pain.008.001.08 (default), pain.008.001.02, pain.008.003.02
pain.007pain.007.001.09 — the only version SEPA defines
camt.055 / camt.029camt.055.001.05 / camt.029.001.06 — the pair the DFÜ-Abkommen names

Select one with Pain001Builder::schema / Pain008Builder::schema. Both enums implement FromStr over the message identifier and the namespace URN, so the target version can come from configuration:

use sepa::pain008::DirectDebitSchema;
let schema: DirectDebitSchema = "pain.008.001.02".parse()?;

CI validates generated fixtures against pinned ISO XSDs for each emitted schema version. Builders do not run an XSD validator for each output.

§These are deliberately not ISO’s newest versions

Select the version required by the applicable scheme profile and your bank, not simply the newest ISO definition. Address requirements are independent of message-version selection.

§Regulatory references

StandardModuleUsage
ISO 13616-1 + SWIFT IBAN RegistryibanIBAN validation + country-length registry
EPC409-09 v8.0ibanSEPA scheme country list
ISO 9362bicBIC/SWIFT validation
ISO 3166-1 alpha-2countryCountry codes for BICs and addresses
EPC153-22 v2.2addressStructured and hybrid addresses
EPC262-08creditor_idCreditor Identifier check digits
ISO 20022 pain.001pain001SEPA Credit Transfer (SCT + SCT Inst)
ISO 20022 pain.008pain008SEPA Direct Debit (CORE + B2B)
ISO 20022 pain.007pain007SEPA Direct Debit reversal
ISO 20022 pain.002pain002Payment Status Report
DK Anlage 3 V3.9pain002DK-profile Verification of Payee outcomes
ISO 20022 camt.052camt052Bank-to-Customer Report (intraday)
ISO 20022 camt.053camt053Bank-to-Customer Statement
ISO 20022 camt.054camt054Payment notifications
ISO 20022 camt.055camt055Payment Cancellation Request (recall)
ISO 20022 camt.029camt029Resolution of Investigation
EPC217-08charsetSEPA character set + conversion table
EPC SEPA Rulebooks 2023/2025allGoverns all SEPA transactions

§Quick start

use sepa::{
    CreditTransferEntry, CreditTransferGroup, DirectDebitEntry, DirectDebitGroup, IsoDate,
    Pain001Builder, Pain008Builder, SequenceType, validate_creditor_id, validate_iban,
};

let iban = validate_iban("DE89 3704 0044 0532 0130 00")?;
assert_eq!(iban.as_str(), "DE89370400440532013000");
assert_eq!(iban.to_string(), "DE89 3704 0044 0532 0130 00");

// pain.001 — Credit Transfer (Überweisung)
let ct_xml = Pain001Builder::new("Debtor GmbH", "CT-2026-07-001")
    .add_group(
        CreditTransferGroup::new("Debtor GmbH", &iban, IsoDate::new(2026, 7, 20)?)
            .add_entry(CreditTransferEntry::new(
                "Supplier AG", iban.clone(), 12_000, "INV-2026-001",
            )),
    )
    .build()?;
assert!(ct_xml.contains("pain.001.001.09"));

// pain.008 — a direct debit run carrying FRST and RCUR in one file.
let ci = validate_creditor_id("DE98ZZZ09999999999")?;
let dd_xml = Pain008Builder::new("Creditor GmbH", "DD-2026-07-001")
    .add_group(
        DirectDebitGroup::new("Creditor GmbH", &iban, &ci, IsoDate::new(2026, 7, 20)?)
            .sequence_type(SequenceType::Frst)
            .add_entry(DirectDebitEntry::new(
                "MND-1", "2026-06-01".parse()?, "Neu Kunde", iban.clone(), 5_000, "R-001",
            )),
    )
    .add_group(
        DirectDebitGroup::new("Creditor GmbH", &iban, &ci, IsoDate::new(2026, 7, 18)?)
            .sequence_type(SequenceType::Rcur)
            .add_entry(DirectDebitEntry::new(
                "MND-2", "2024-06-01".parse()?, "Alt Kunde", iban.clone(), 7_500, "R-002",
            )),
    )
    .build()?;
assert!(dd_xml.contains("<SeqTp>FRST</SeqTp>"));
assert!(dd_xml.contains("<SeqTp>RCUR</SeqTp>"));

§Payment groups

A message carries one or more CreditTransferGroup / DirectDebitGroup blocks, each becoming a PmtInf. Sequence type, execution date, debtor account, batch booking and category purpose all live at that level, so several groups are what let a single file mix FRST with RCUR, or carry two execution dates — rather than forcing a separate submission per combination.

§Validation

Pain001Builder::build and Pain008Builder::build return a Result: they enforce the EPC field rules that the XSD does not — amount range, identifier and name lengths, non-empty batches, schema-feature compatibility — and by default transliterate text into the SEPA character set, so Müller & Söhne is emitted as Mueller + Soehne. See validate and charset.

Failures come back as a BuildError: a typed ValidationError naming the ISO 20022 element, plus the Location — group and transaction index — it came from, so a rejected collection run points at the row to fix.

Two classes of mistake are absent from that list because they cannot fail there — they are unrepresentable rather than caught late. IsoDate validates at construction, so an impossible ReqdColltnDt never reaches a batch; and PostalAddress requires a town and a country for this library’s structured/hybrid address scope. Other address fields are checked at use. This representation limit is not a claim that schemes universally refuse unstructured addresses.

§Nothing that matters is defaulted from a clock

MsgId and the payment date are constructor arguments, because neither has a safe default. MsgId is the key a bank de-duplicates submissions by, so it has to come from a sequence that survives a restart. ReqdExctnDt and ReqdColltnDt are the day money leaves an account, which depends on the scheme, the sequence type, TARGET2 and the bank’s cut-off — a banking-calendar question this crate cannot answer.

One implicit clock read remains: GrpHdr/CreDtTm. Pin it with created_at() and a submitted file regenerates byte-for-byte.

§Undoing a payment

Three messages undo one, and they are not interchangeable — picking the wrong one wastes the window in which anything can still be done:

You want toMessageNote
Stop a file you just sentcamt055A request. The bank may refuse; the answer is camt029
Give back a collection that settledpain007An instruction, and only for a direct debit
Learn a payment came backcamt054The bank telling you, after the fact
use sepa::{Camt055Builder, CancellationEntry, CancellationGroup, CancellationReason,
           OriginalMessage, Pain008Builder, parse_camt029, validate_bic};

let recall = Camt055Builder::new(
    "CXL-2026-07-001",
    "Stadtwerke GmbH",
    validate_bic("COBADEFFXXX")?,
    OriginalMessage::from_direct_debit(&submitted),
)
.add_group(
    CancellationGroup::new("PMT-2026-07-A")
        .add_entry(CancellationEntry::new("E2E-1", CancellationReason::Dupl)),
);

Nothing has been cancelled until the camt.029 says so, and PDCR — pending — is neither answer. Camt029Document::is_final is what separates the two.

§Reading bank files

Every parser returns a Result with a typed error naming what it could not read — a reconciliation import can always say why it skipped a row, not just that it did. For a batch-booked camt entry, read EntryDetail::signed_ct per transaction and check CashEntry::details_reconcile before posting; CashEntry::batch carries the PmtInfId of the group you submitted, which is what matches a booking back to your own file.

Decoded observations are retained alongside typed interpretations: CashEntry::booking_date returns an IsoDate and booking_date_raw the text that arrived, so a non-conforming file is readable rather than rejected.

Two things a ledger needs are reported beside the amount rather than inside it. CashEntry::charges carries the return fee on a bounced collection — check Charges::all_included_in_amount before posting it, since a charge already inside the entry amount must not be booked twice. And a pain.002 rejection explains itself at exactly one of three levels depending on how far the bank got, so Pain002Document::reason_codes gathers all of them: a submission refused outright carries its reason at group level with no transaction blocks to inspect at all.

Re-exports§

pub use address::AddressError;
pub use address::AddressFormat;
pub use address::PostalAddress;
pub use bic::Bic;
pub use bic::BicError;
pub use bic::BicPattern;
pub use bic::validate_bic;
pub use camt::AccountRef;
pub use camt::BalanceType;
pub use camt::BatchInfo;
pub use camt::CashEntry;
pub use camt::ChargeRecord;
pub use camt::Charges;
pub use camt::EntryDetail;
pub use camt::EntryStatus;
pub use camt::StatementBalance;
pub use camt029::Camt029Document;
pub use camt029::Camt029ParseError;
pub use camt029::CancellationCount;
pub use camt029::CancellationStatus;
pub use camt029::GroupCancellationStatus;
pub use camt029::PaymentInfoCancellationStatus;
pub use camt029::RejectionReason;
pub use camt029::ResolutionOutcome;
pub use camt029::TransactionCancellationStatus;
pub use camt029::parse_camt029;
pub use camt029::parse_camt029_with_limits;
pub use camt052::Camt052Document;
pub use camt052::Camt052ParseError;
pub use camt052::Camt052Report;
pub use camt052::parse_camt052;
pub use camt052::parse_camt052_with_limits;
pub use camt053::Camt053Document;
pub use camt053::Camt053ParseError;
pub use camt053::Camt053Statement;
pub use camt053::parse_camt053;
pub use camt053::parse_camt053_with_limits;
pub use camt054::Camt054Document;
pub use camt054::Camt054Notification;
pub use camt054::Camt054ParseError;
pub use camt054::CreditDebitIndicator;
pub use camt054::UnknownIndicator;
pub use camt054::parse_camt054;
pub use camt054::parse_camt054_with_limits;
pub use camt055::Camt055Builder;
pub use camt055::CancellationEntry;
pub use camt055::CancellationGroup;
pub use camt055::CancellationReason;
pub use camt055::CaseParty;
pub use camt055::OriginalMessage;
pub use charset::Transliteration;
pub use charset::is_sepa_text;
pub use charset::transliterate;
pub use country::is_country_code;
pub use creditor_id::CreditorId;
pub use creditor_id::CreditorIdError;
pub use creditor_id::creditor_id_check_digits;
pub use creditor_id::validate_creditor_id;
pub use currency::Currency;
pub use currency::CurrencyError;
pub use date::ConversionError;
pub use date::DateError;
pub use date::DateTimeError;
pub use date::IsoDate;
pub use date::IsoDateTime;
pub use iban::BbanCharClass;
pub use iban::Iban;
pub use iban::IbanError;
pub use iban::iban_bban_format;
pub use iban::iban_check_digits;
pub use iban::iban_country_length;
pub use iban::is_sepa_country;
pub use iban::validate_iban;
pub use pain001::ChargeBearer;
pub use pain001::CreditTransferEntry;
pub use pain001::CreditTransferGroup;
pub use pain001::CreditTransferKind;
pub use pain001::CreditTransferSchema;
pub use pain001::ExecutionMoment;
pub use pain001::Pain001Builder;
pub use pain002::OriginalMessageType;
pub use pain002::Pain002Document;
pub use pain002::Pain002ParseError;
pub use pain002::PaymentInfoStatus;
pub use pain002::PaymentStatus;
pub use pain002::ReasonCode;
pub use pain002::StatusCount;
pub use pain002::TransactionStatus;
pub use pain002::VerificationOutcome;
pub use pain002::parse_pain002;
pub use pain002::parse_pain002_with_limits;
pub use pain007::OriginalCollection;
pub use pain007::Pain007Builder;
pub use pain007::ReversalEntry;
pub use pain007::ReversalGroup;
pub use pain007::ReversalReason;
pub use pain008::DirectDebitEntry;
pub use pain008::DirectDebitGroup;
pub use pain008::DirectDebitSchema;
pub use pain008::DirectDebitScheme;
pub use pain008::MandateAmendment;
pub use pain008::Pain008Builder;
pub use pain008::SequenceType;
pub use pain008::UnknownSequenceType;
pub use party::IdentifierKind;
pub use party::Party;
pub use party::PartyIdentifier;
pub use purpose::CategoryPurpose;
pub use purpose::Purpose;
pub use purpose::PurposeCodeError;
pub use reference::RemittanceInfo;
pub use reference::RfReference;
pub use reference::RfReferenceError;
pub use validate::BuildError;
pub use validate::CharsetPolicy;
pub use validate::Location;
pub use validate::UnknownSchema;
pub use validate::ValidationError;
pub use validate::WriteError;

Modules§

address
Postal addresses — ISO 20022 PstlAdr.
bic
BIC (Business Identifier Code) validation — ISO 9362:2022.
camt
Shared vocabulary for the camt.05x cash-management messages.
camt029
ISO 20022 camt.029 — Resolution of Investigation: the answer to a recall.
camt052
ISO 20022 camt.052 — Bank-to-Customer Account Report (intraday).
camt053
ISO 20022 camt.053 — Bank-to-Customer Statement parser.
camt054
ISO 20022 camt.054 — Bank-to-Customer Debit/Credit Notification.
camt055
ISO 20022 camt.055 — Customer Payment Cancellation Request (recall).
charset
SEPA Basic Latin character set — validation and transliteration.
country
ISO 3166-1 alpha-2 country codes, as ISO 20022 uses them.
creditor_id
SEPA Creditor Identifier (CI) — EPC AT-02.
currency
ISO 4217 currency codes, as ISO 20022 constrains them.
date
Typed ISO 8601 dates and timestamps for the ISO 20022 date fields.
iban
IBAN validation — ISO 13616-1:2007 checksum and national structure.
pain001
ISO 20022 pain.001 — credit transfer initiation, for three EPC schemes.
pain002
ISO 20022 pain.002 — Customer Payment Status Report parser.
pain007
ISO 20022 pain.007 — SEPA Direct Debit reversal.
pain008
ISO 20022 pain.008 — SEPA Direct Debit initiation.
party
Ultimate parties — UltmtDbtr and UltmtCdtr.
purpose
ISO 20022 purpose codes — Purp and CtgyPurp.
reference
ISO 11649 RF Creditor Reference and structured remittance information.
validate
EPC field-level rules for outgoing pain.001 / pain.008 messages.

Structs§

ParseLimits
Finite resource limits applied before retaining XML message data.

Enums§

AmountError
Error returned when a decimal amount string cannot be read as integer cents.
XmlError
Error returned when an XML document cannot be parsed.

Functions§

ct_from_eur_str
Parse a "1234.56" EUR string into integer cents — pure integer arithmetic, no f64.
ct_to_eur_str
Format ct (1/100 EUR) as "1234.56" — pure integer arithmetic, no f64.