/// <summary> /// processing CAMT file /// </summary> public void ProcessFileContent(string content, bool AParsePreviousYear, out TVerificationResultCollection AVerificationResult) { statements = new List <TStatement>(); AVerificationResult = new TVerificationResultCollection(); CultureInfo backupCulture = Thread.CurrentThread.CurrentCulture; try { XmlDocument doc = new XmlDocument(); doc.LoadXml(content); XmlNode nodeDocument = doc.DocumentElement; string CAMTVersion = "CAMT.53"; XmlNamespaceManager nsmgr = GetNamespaceManager(doc, "urn:iso:std:iso:20022:tech:xsd:camt.053.001.02"); if (nsmgr == null) { CAMTVersion = "CAMT.52"; nsmgr = GetNamespaceManager(doc, "urn:iso:std:iso:20022:tech:xsd:camt.052.001.02"); } if (nsmgr == null) { throw new Exception("expecting xmlns for CAMT.52 or CAMT.53"); } XmlNodeList stmts = null; if (CAMTVersion == "CAMT.53") { stmts = nodeDocument.SelectNodes("camt:BkToCstmrStmt/camt:Stmt", nsmgr); } else if (CAMTVersion == "CAMT.52") { stmts = nodeDocument.SelectNodes("camt:BkToCstmrAcctRpt/camt:Rpt", nsmgr); } Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; foreach (XmlNode nodeStatement in stmts) { TStatement stmt = new TStatement(); stmt.id = nodeStatement.SelectSingleNode("camt:ElctrncSeqNb", nsmgr).InnerText; stmt.accountCode = nodeStatement.SelectSingleNode("camt:Acct/camt:Id/camt:IBAN", nsmgr).InnerText; stmt.bankCode = nodeStatement.SelectSingleNode("camt:Acct/camt:Svcr/camt:FinInstnId/camt:BIC", nsmgr).InnerText; stmt.currency = nodeStatement.SelectSingleNode("camt:Acct/camt:Ccy", nsmgr).InnerText; Int32 DiffElctrncSeqNb = TAppSettingsManager.GetInt32("DiffElctrncSeqNbFor" + stmt.bankCode + "/" + stmt.accountCode, 0); stmt.id = (Convert.ToInt32(stmt.id) + DiffElctrncSeqNb).ToString(); stmt.severalYears = false; XmlNode nm = nodeStatement.SelectSingleNode("camt:Acct/camt:Ownr/camt:Nm", nsmgr); string ownName = nm != null?nm.InnerText: TAppSettingsManager.GetValue("AccountNameFor" + stmt.bankCode + "/" + stmt.accountCode, String.Empty, false); XmlNodeList nodeBalances = nodeStatement.SelectNodes("camt:Bal", nsmgr); foreach (XmlNode nodeBalance in nodeBalances) { // PRCD: PreviouslyClosedBooked if (nodeBalance.SelectSingleNode("camt:Tp/camt:CdOrPrtry/camt:Cd", nsmgr).InnerText == "PRCD") { stmt.startBalance = Decimal.Parse(nodeBalance.SelectSingleNode("camt:Amt", nsmgr).InnerText); // CreditDebitIndicator: CRDT or DBIT for credit or debit if (nodeBalance.SelectSingleNode("camt:CdtDbtInd", nsmgr).InnerText == "DBIT") { stmt.startBalance *= -1.0m; } stmt.date = DateTime.Parse(nodeBalance.SelectSingleNode("camt:Dt", nsmgr).InnerText); } // CLBD: ClosingBooked else if (nodeBalance.SelectSingleNode("camt:Tp/camt:CdOrPrtry/camt:Cd", nsmgr).InnerText == "CLBD") { stmt.endBalance = Decimal.Parse(nodeBalance.SelectSingleNode("camt:Amt", nsmgr).InnerText); // CreditDebitIndicator: CRDT or DBIT for credit or debit if (nodeBalance.SelectSingleNode("camt:CdtDbtInd", nsmgr).InnerText == "DBIT") { stmt.endBalance *= -1.0m; } stmt.date = DateTime.Parse(nodeBalance.SelectSingleNode("camt:Dt", nsmgr).InnerText); } // ITBD: InterimBooked // CLAV: ClosingAvailable // FWAV: ForwardAvailable } string strDiffBalance = TAppSettingsManager.GetValue("DiffBalanceFor" + stmt.bankCode + "/" + stmt.accountCode, "0"); Decimal DiffBalance = 0.0m; if (Decimal.TryParse(strDiffBalance, out DiffBalance)) { stmt.startBalance += DiffBalance; stmt.endBalance += DiffBalance; } else { TLogging.Log("problem parsing decimal from configuration setting DiffBalanceFor" + stmt.bankCode + "/" + stmt.accountCode); } // if we should parse the transactions only of the past year if (AParsePreviousYear && stmt.date.Month != 12 && stmt.date.Day != 31) { stmt.date = new DateTime(stmt.date.Year - 1, 12, 31); } XmlNodeList nodeEntries = nodeStatement.SelectNodes("camt:Ntry", nsmgr); foreach (XmlNode nodeEntry in nodeEntries) { TTransaction tr = new TTransaction(); tr.inputDate = DateTime.Parse(nodeEntry.SelectSingleNode("camt:BookgDt/camt:Dt", nsmgr).InnerText); tr.valueDate = DateTime.Parse(nodeEntry.SelectSingleNode("camt:ValDt/camt:Dt", nsmgr).InnerText); if (tr.valueDate.Year != stmt.date.Year) { // ignore transactions that are in a different year than the statement stmt.severalYears = true; continue; } tr.amount = Decimal.Parse(nodeEntry.SelectSingleNode("camt:Amt", nsmgr).InnerText); if (nodeEntry.SelectSingleNode("camt:Amt", nsmgr).Attributes["Ccy"].Value != stmt.currency) { throw new Exception("transaction currency " + nodeEntry.SelectSingleNode("camt:Amt", nsmgr).Attributes["Ccy"].Value + " does not match the bank statement currency"); } if (nodeEntry.SelectSingleNode("camt:CdtDbtInd", nsmgr).InnerText == "DBIT") { tr.amount *= -1.0m; } XmlNode desc = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RmtInf/camt:Ustrd", nsmgr); tr.description = desc != null?desc.InnerText:String.Empty; XmlNode partnerName = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:Dbtr/camt:Nm", nsmgr); if (partnerName != null) { tr.partnerName = partnerName.InnerText; } XmlNode accountCode = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:DbtrAcct/camt:Id/camt:IBAN", nsmgr); if (accountCode != null) { tr.accountCode = accountCode.InnerText; } XmlNode CrdtName = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:Cdtr/camt:Nm", nsmgr); XmlNode DbtrName = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:Dbtr/camt:Nm", nsmgr); if ((CrdtName != null) && (CrdtName.InnerText != ownName)) { if ((DbtrName != null) && (DbtrName.InnerText == ownName)) { // we are the debitor } else if (ownName != String.Empty) { // sometimes donors write the project or recipient in the field where the organisation is supposed to be TLogging.Log("CrdtName is not like expected: " + tr.description + " --- " + CrdtName.InnerText); tr.description += " " + CrdtName.InnerText; } } XmlNode EndToEndId = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:Refs/camt:EndToEndId", nsmgr); if ((EndToEndId != null) && (EndToEndId.InnerText != "NOTPROVIDED") && !EndToEndId.InnerText.StartsWith("LS-") && !EndToEndId.InnerText.StartsWith("ZV") && !EndToEndId.InnerText.StartsWith("IZV-DISPO")) { // sometimes donors write the project or recipient in unexpected field TLogging.Log("EndToEndId: " + tr.description + " --- " + EndToEndId.InnerText); tr.description += " " + EndToEndId.InnerText; } // eg NSTO+152+00900. look for SEPA Geschäftsvorfallcodes // see the codes: https://www.wgzbank.de/export/sites/wgzbank/de/wgzbank/downloads/produkte_leistungen/firmenkunden/zv_aktuelles/Uebersicht-GVC-und-Buchungstexte-WGZ-BANK_V062015.pdf string[] GVCCode = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:BkTxCd/camt:Prtry/camt:Cd", nsmgr).InnerText.Split( new char[] { '+' }); tr.typecode = GVCCode[1]; // for SEPA direct debit batches, there are multiple TxDtls records XmlNodeList transactionDetails = nodeEntry.SelectNodes("camt:NtryDtls/camt:TxDtls", nsmgr); if (transactionDetails.Count > 1) { tr.partnerName = String.Empty; tr.description = String.Format( Catalog.GetString("SEPA Sammel-Basislastschrift mit {0} Lastschriften"), transactionDetails.Count); } stmt.transactions.Add(tr); TLogging.LogAtLevel(2, "count : " + stmt.transactions.Count.ToString()); } statements.Add(stmt); } } catch (Exception e) { AVerificationResult.Add(new TVerificationResult(null, "problem with importing camt file; " + e.Message, TResultSeverity.Resv_Critical)); TLogging.Log("problem with importing camt file; " + e.Message + Environment.NewLine + e.StackTrace); } finally { Thread.CurrentThread.CurrentCulture = backupCulture; } }
private void HandleSwiftData(string swiftTag, string swiftData) { if (swiftTag == "OS") { // ignore } else if (swiftTag == "20") { // 20 is used for each "page" of the statement; but we want to put all transactions together // the whole statement closes with 62F if (currentStatement == null) { currentStatement = new TStatement(); // currentStatement.lines.Add(new TLine(swiftTag, swiftData)); } } else if (swiftTag == "25") { int posSlash = swiftData.IndexOf("/"); currentStatement.bankCode = swiftData.Substring(0, posSlash); currentStatement.accountCode = WithoutLeadingZeros(swiftData.Substring(posSlash + 1)); } else if (swiftTag.StartsWith("60")) { // 60M is the start balance on each page of the statement. // 60F is the start balance of the whole statement. // first character is D or C int DebitCreditIndicator = (swiftData[0] == 'D' ? -1 : +1); // next 6 characters: YYMMDD // next 3 characters: currency // last characters: balance with comma for decimal point currentStatement.currency = swiftData.Substring(7, 3); decimal balance = DebitCreditIndicator * Convert.ToDecimal(swiftData.Substring(10).Replace(",", Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyDecimalSeparator)); // we only want to use the first start balance if (swiftTag == "60F") { currentStatement.startBalance = balance; currentStatement.endBalance = balance; } else { // check if the balance inside the statement is ok // ie it fits the balance of the previous page if (Convert.ToDecimal(Math.Round(currentStatement.endBalance, 2)) != Convert.ToDecimal(balance)) { throw new Exception("start balance does not match current balance"); } } } else if (swiftTag == "28C") { // this contains the number of the statement and the number of the page // only use for first page if (currentStatement.transactions.Count == 0) { if (swiftData.IndexOf("/") != -1) { currentStatement.id = swiftData.Substring(0, swiftData.IndexOf("/")); } else { // realtime statement. do not use statement number 0, because Sparkasse has 0/1 for valid statements currentStatement.id = string.Empty; } } } else if (swiftTag == "61") { TTransaction transaction = new TTransaction(); // valuta date (YYMMDD) try { transaction.valueDate = new DateTime(2000 + Convert.ToInt32(swiftData.Substring(0, 2)), Convert.ToInt32(swiftData.Substring(2, 2)), Convert.ToInt32(swiftData.Substring(4, 2))); } catch (ArgumentOutOfRangeException) { // we have had the situation in the bank file with a date 30 Feb 2010. // probably because the instruction by the donor is to transfer the money on the 30 day each month // use the last day of the month int year = 2000 + Convert.ToInt32(swiftData.Substring(0, 2)); int month = Convert.ToInt32(swiftData.Substring(2, 2)); int day = DateTime.DaysInMonth(year, month); transaction.valueDate = new DateTime(year, month, day); } swiftData = swiftData.Substring(6); if (char.IsDigit(swiftData[0])) { // posting date (MMDD) transaction.inputDate = new DateTime(transaction.valueDate.Year, Convert.ToInt32(swiftData.Substring(0, 2)), Convert.ToInt32(swiftData.Substring(2, 2))); swiftData = swiftData.Substring(4); } else { transaction.inputDate = transaction.valueDate; } // debit or credit, or storno debit or credit int debitCreditIndicator = 0; if (swiftData[0] == 'R') { // storno means: reverse the debit credit flag debitCreditIndicator = (swiftData[1] == 'D' ? 1 : -1); swiftData = swiftData.Substring(2); } else { debitCreditIndicator = (swiftData[0] == 'D' ? -1 : 1); swiftData = swiftData.Substring(1); } // sometimes there is something about currency if (Char.IsLetter(swiftData[0])) { // just skip it for the moment swiftData = swiftData.Substring(1); } // the amount, finishing with N transaction.amount = debitCreditIndicator * Convert.ToDecimal(swiftData.Substring(0, swiftData.IndexOf("N")).Replace(",", Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyDecimalSeparator)); currentStatement.endBalance += transaction.amount; swiftData = swiftData.Substring(swiftData.IndexOf("N")); // Geschaeftsvorfallcode // transaction.typecode = swiftData.Substring(1, 3); swiftData = swiftData.Substring(4); // the following sub fields are ignored // optional: customer reference; ends with // // optional: bank reference; ends with CR/LF // something else about original currency and transaction fees currentStatement.transactions.Add(transaction); } else if (swiftTag == "86") { TTransaction transaction = currentStatement.transactions[currentStatement.transactions.Count - 1]; // Geschaeftsvorfallcode transaction.typecode = swiftData.Substring(0, 3); swiftData = swiftData.Substring(3); char separator = swiftData[0]; swiftData = swiftData.Substring(1); string[] elements = swiftData.Split(new char[] { separator }); foreach (string element in elements) { int key = 0; string value = element; try { key = Convert.ToInt32(element.Substring(0, 2)); value = element.Substring(2); } catch (Exception) { // if there is a question mark in the description, then we get here } if (key == 0) { // Buchungstext transaction.text = value; } else if (key == 10) { // Primanotennummer; ignore at the moment } else if ((key >= 11) && (key <= 19)) { // ignore, unknown meaning } else if ((key >= 20) && (key <= 29)) { // do not insert a space between description lines transaction.description += value; } else if (key == 30) { transaction.bankCode = value; } else if (key == 31) { // could use WithoutLeadingZeros, but then this must be stored in p_banking_details also without leading zeros // which is not the case at the moment, and would increase complexity of sql queries transaction.accountCode = value; } else if ((key == 32) || (key == 33)) { transaction.partnerName += value; } else if (key == 34) { // Textschlüsselergänzung; ignore } else if ((key == 35) || (key == 36)) { // Empfängername transaction.description += value; } else if ((key >= 60) && (key <= 63)) { transaction.description += value; } else { throw new Exception("unknown key " + key.ToString()); } } } else if (swiftTag.StartsWith("62")) { // 62M: finish page // 62F: finish statement int debitCreditIndicator = (swiftData[0] == 'D' ? -1 : 1); swiftData = swiftData.Substring(1); // posting date YYMMDD DateTime postingDate = new DateTime(2000 + Convert.ToInt32(swiftData.Substring(0, 2)), Convert.ToInt32(swiftData.Substring(2, 2)), Convert.ToInt32(swiftData.Substring(4, 2))); swiftData = swiftData.Substring(6); // currency swiftData = swiftData.Substring(3); // sometimes, this line is the last line, and it has -NULNULNUL at the end if (swiftData.Contains("-\0")) { swiftData = swiftData.Substring(0, swiftData.IndexOf("-\0")); } // end balance decimal shouldBeBalance = debitCreditIndicator * Convert.ToDecimal(swiftData.Replace(",", Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyDecimalSeparator)); currentStatement.endBalance = Math.Round(currentStatement.endBalance, 2); if (Convert.ToDecimal(Math.Round(currentStatement.endBalance, 2)) != Convert.ToDecimal(shouldBeBalance)) { throw new Exception("end balance does not match" + " last transaction was: " + currentStatement.transactions[currentStatement.transactions.Count - 1].partnerName + " balance is: " + currentStatement.endBalance.ToString() + " but should be: " + shouldBeBalance.ToString()); } if (swiftTag == "62F") { currentStatement.date = postingDate; statements.Add(currentStatement); currentStatement = null; } } else if (swiftTag == "64") { // valutensaldo; ignore } else if (swiftTag == "65") { // future valutensaldo; ignore } else { Console.WriteLine("swiftTag " + swiftTag + " is unknown"); } }