Skip to content

Instantly share code, notes, and snippets.

@slyvain
Last active September 11, 2023 21:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save slyvain/8e8355ff6d4065e8505186255e22090f to your computer and use it in GitHub Desktop.
Save slyvain/8e8355ff6d4065e8505186255e22090f to your computer and use it in GitHub Desktop.
Generate an XML file compliant with SEPA Credit Transfer (SCT) in PAIN 001.001.003 format for the EUR currency.

Introduction

SEPA data formats are based on the ISO 20022 Standard.

The usual banking file format for a SEPA & Non-SEPA Credit Transfer is the Pain (payment initiation) format 001.001.03.

However there is a specific German credit transfer XML format that is supported only by a limited amount of banks, the Pain 001.003.03, but since November 2016, the recommended Standard is 001.001.03.

For more on this, see the german specifications in section DFÜ Agreement Annex 3 – Version 3.0, Chapter 4.

Documentation

Online validation tools

Generic XML
SEPA
/// <summary>
/// Generate an XML document containing a SEPA Credit Transfer (SCT)
/// </summary>
/// <param name="refunds">Collection of refunds from the debtor (a company) to the creditor (a customer)</param>
/// <returns>XML document</returns>
private XDocument GenerateXMLFile(IEnumerable<RefundObject> refunds)
{
XDocument document = null;
// Get the correct namespace to build the Pain 001.001.03 document
XNamespace xsi = XNamespace.Get("http://www.w3.org/2001/XMLSchema-instance");
XNamespace xmlns = "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03";
// The namespace needs to prefix all the element names down the tree. There is no "inheritance" of namespace in XML, therefore
// use this anonymous function to concatenate namespace and element name.
// On the long run, it makes the code (a little) more readable.
// For example:
// new XElement(ns("CreDtTm"), DateTime.Now.ToString("yyyy-MM-ddThh:mm:ss")),
// instead of
// new XElement(xmlns + "CreDtTm", DateTime.Now.ToString("yyyy-MM-ddThh:mm:ss")),
Func<string, XName> ns = elementName => xmlns + elementName;
// get the payment details elements to add in the tree
var paymentDetails = this.GetPaymentDetails(refunds, ns);
document = new XDocument(new XDeclaration("1.0", Encoding.UTF8.BodyName, "yes"),
new XElement(ns("Document"),
new XAttribute(XNamespace.Xmlns + "xsi", xsi),
new XElement(ns("CstmrCdtTrfInitn"),
// Group Header
new XElement(ns("GrpHdr"),
new XElement(ns("MsgId"), ["Generate_Unique_ID"]),
new XElement(ns("CreDtTm"), DateTime.Now.ToString("yyyy-MM-ddThh:mm:ss")),
new XElement(ns("NbOfTxs"), refunds.Count()),
new XElement(ns("CtrlSum"), refunds.Sum(x => x.Amount).ToString("#0.00", CultureInfo.InvariantCulture)),
new XElement(ns("InitgPty"),
new XElement(ns("Nm"), "[Name_Of_Initiating_Party]"))
), // group header
// add all the payment elements
paymentDetails.ToArray()
) // CstmrCdtTrfInitn
) // Document xmlns
);
return document;
}
/// <summary>
/// Get the batch of payments to the creditors
/// </summary>
/// <param name="refunds">Collection of refunds</param>
/// <param name="ns">Delegate method to concatenate the namespace with the element name</param>
/// <returns>XML formatted payment batches</returns>
private IEnumerable<XElement> GetPaymentDetails(IEnumerable<RefundObject> refunds, Func<string, XName> ns)
{
List<XElement> xelements = new List<XElement>();
foreach (var refund in refunds)
{
// format the data before
var endtoendid = refund.EndToEndId.HasValue ? refund.EndToEndId : "NOTPROVIDED";
// add the refund info in the file
xelements.Add(new XElement(ns("PmtInf"),
new XElement(ns("PmtInfId"), refund.Id),
new XElement(ns("PmtMtd"), "TRF"), // Payment method => always TRF = transfer
new XElement(ns("BtchBookg"), "true"), // True => bulk posting, False => single posting
new XElement(ns("NbOfTxs"), 1),
new XElement(ns("CtrlSum"), refund.Amount),
new XElement(ns("PmtTpInf"),
new XElement(ns("SvcLvl"),
new XElement(ns("Cd"), "SEPA"))), // Payment Type Info => SEPA
new XElement(ns("ReqdExctnDt"), DateTime.Now.ToString("yyyy-MM-dd")), // Requested execution date
new XElement(ns("Dbtr"),
new XElement(ns("Nm"), "[Debtor_Name]")),
new XElement(ns("DbtrAcct"),
new XElement(ns("Id"),
new XElement(ns("IBAN"), "[Debtor_IBAN]"))),
new XElement(ns("DbtrAgt"),
new XElement(ns("FinInstnId"),
new XElement(ns("BIC"), "[Debtor_BIC]"))),
new XElement(ns("ChrgBr"), "SLEV"), // Charge bearer => always "SLEV" in SEPA = share
new XElement(ns("CdtTrfTxInf"),
new XElement(ns("PmtId"),
new XElement(ns("EndToEndId"), endtoendid)),
new XElement(ns("Amt"),
new XElement(ns("InstdAmt"),
new XAttribute("Ccy", "EUR"), refund.Amount)), // EUR
new XElement(ns("CdtrAgt"),
new XElement(ns("FinInstnId"),
new XElement(ns("BIC"), "[Creditor_BIC]"))),
new XElement(ns("Cdtr"),
new XElement(ns("Nm"), "[Creditor_Name]")),
new XElement(ns("CdtrAcct"),
new XElement(ns("Id"),
new XElement(ns("IBAN"), "[Creditor_IBAN]"))),
new XElement(ns("RmtInf"),
new XElement(ns("Ustrd"), "[Remittance information]")) // 140 char max
) // CdtTrfTxInf
)); // PmtInf
}
return xelements;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment