slyvain
10/22/2018 - 2:01 PM

SEPA Credit Transfer (SCT) file building

Generate an XML file compliant with SEPA Credit Transfer (SCT) in PAIN 001.001.003 format for the EUR currency.

/// <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;
}

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