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 hybridPstlAdr - 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/IsoDateTimefor 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:
| Message | Variants |
|---|---|
| pain.001 | pain.001.001.09 (default), pain.001.001.03, pain.001.003.03 |
| pain.008 | pain.008.001.08 (default), pain.008.001.02, pain.008.003.02 |
| pain.007 | pain.007.001.09 — the only version SEPA defines |
| camt.055 / camt.029 | camt.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
| Standard | Module | Usage |
|---|---|---|
| ISO 13616-1 + SWIFT IBAN Registry | iban | IBAN validation + country-length registry |
| EPC409-09 v8.0 | iban | SEPA scheme country list |
| ISO 9362 | bic | BIC/SWIFT validation |
| ISO 3166-1 alpha-2 | country | Country codes for BICs and addresses |
| EPC153-22 v2.2 | address | Structured and hybrid addresses |
| EPC262-08 | creditor_id | Creditor Identifier check digits |
| ISO 20022 pain.001 | pain001 | SEPA Credit Transfer (SCT + SCT Inst) |
| ISO 20022 pain.008 | pain008 | SEPA Direct Debit (CORE + B2B) |
| ISO 20022 pain.007 | pain007 | SEPA Direct Debit reversal |
| ISO 20022 pain.002 | pain002 | Payment Status Report |
| DK Anlage 3 V3.9 | pain002 | DK-profile Verification of Payee outcomes |
| ISO 20022 camt.052 | camt052 | Bank-to-Customer Report (intraday) |
| ISO 20022 camt.053 | camt053 | Bank-to-Customer Statement |
| ISO 20022 camt.054 | camt054 | Payment notifications |
| ISO 20022 camt.055 | camt055 | Payment Cancellation Request (recall) |
| ISO 20022 camt.029 | camt029 | Resolution of Investigation |
| EPC217-08 | charset | SEPA character set + conversion table |
| EPC SEPA Rulebooks 2023/2025 | all | Governs 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 to | Message | Note |
|---|---|---|
| Stop a file you just sent | camt055 | A request. The bank may refuse; the answer is camt029 |
| Give back a collection that settled | pain007 | An instruction, and only for a direct debit |
| Learn a payment came back | camt054 | The 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 —
UltmtDbtrandUltmtCdtr. - purpose
- ISO 20022 purpose codes —
PurpandCtgyPurp. - reference
- ISO 11649 RF Creditor Reference and structured remittance information.
- validate
- EPC field-level rules for outgoing pain.001 / pain.008 messages.
Structs§
- Parse
Limits - Finite resource limits applied before retaining XML message data.
Enums§
- Amount
Error - 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.