This repository has been archived on 2024-11-28. You can view files and clone it, but cannot push or open issues or pull requests.
LuisLeon_FactuGES2/Source/Modulos/Banca electronica/Utiles/SEPAUnit/SEPACreditTransfer.pas

566 lines
20 KiB
ObjectPascal

//
// Delphi unit for SEPA credit transfer XML file creation
// (beta version 0.2.2, 2014-02-27)
//
// Copyright (C) 2013-2014 by Aaron Spettl
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// Author: Aaron Spettl
// Virchowstr. 26
// 89075 Ulm
// Germany
// E-mail: aaron@spettl.de
//
unit SEPACreditTransfer;
{$IFDEF FPC} // Lazarus: set compiler mode and file encoding
{%encoding CP1252}
{$mode objfpc}{$H+}
{$ENDIF}
interface
uses
SysUtils, Classes, DateUtils, SEPACommon;
type
// In the following, all necessary classes to create credit transfers for
// SEPA XML files are introduced. Please have a look at the specification of
// the XML data format at
// http://www.ebics.de/index.php?id=77
// (section 2.2.2, "Anlage3_Datenformate_V2.7.pdf" by EBICS, Die Deutsche Kreditwirtschaft).
//
// Short explanation of XML file for credit transfers:
//
// XML tags corresponding class
// ---------------------------------------------------------------------------
// <Document> TCreditTransferInitiation
// <CstmrCdtTrfInitn> TCreditTransferInitiation
// <PmtInf> TCreditTransferPaymentInformation
// <CdtTrfTxInf> TCreditTransferTransactionInformation
// <CdtTrfTxInf> ...
// ...
// <PmtInf>
// ...
//
// Note that all strings in these units are interpreted with respect to the
// default behavior of the development environment, i.e.,
// a) for Delphi < 2009: ANSI strings
// b) for Delphi >= 2009: Unicode strings
// c) for Lazarus: no encoding specified, ANSI is assumed
TCreditTransferTransactionInformation = class
private
fPmtIdEndToEndId: String; // end-to-end identification of this payment (by default "NOTPROVIDED")
fInstdAmtCcy: String; // instructed amount, currency (always "EUR")
fInstdAmt: Currency; // instructed amount
fCdtrAgt: TFinancialInstitution; // creditor agent
fCdtrNm: String; // creditor name
fCdtrAcct: TAccountIdentification; // creditor account identification
fRmtInfUstrd: String; // unstructured remittance information
procedure SetCdtrNm(const str: String);
procedure SetRmtInfUstrd(const str: String);
public
constructor Create;
destructor Destroy; override;
property PmtIdEndToEndId: String read fPmtIdEndToEndId write fPmtIdEndToEndId;
property InstdAmtCcy: String read fInstdAmtCcy write fInstdAmtCcy;
property InstdAmt: Currency read fInstdAmt write fInstdAmt;
property CdtrAgt: TFinancialInstitution read fCdtrAgt;
property CdtrNm: String read fCdtrNm write SetCdtrNm;
property CdtrAcct: TAccountIdentification read fCdtrAcct;
property RmtInfUstrd: String read fRmtInfUstrd write SetRmtInfUstrd;
function Validate(const schema: String; const appendTo: TStringList = nil): TStringList;
procedure SaveToStream(const stream: TStream; const schema: String);
end;
TCreditTransferPaymentInformation = class
private
fPmtInfId: String; // payment information identification
fPmtMtd: String; // payment method (always "TRF")
fPmtTpInfSvcLvlCd: String; // payment type, service level code (always "SEPA")
fPmtTpInfInstrPrty: String; // payment type, instruction priority ("NORM" or "HIGH")
fReqdExctnDt: TDateTime; // requested execution date
fDbtrNm: String; // creditor name
fDbtrAcct: TAccountIdentification; // creditor account identification
fDbtrAgt: TFinancialInstitution; // creditor agent
fChrgBr: String; // charge bearer (always "SLEV")
fCdtTrfTxInf: array of TCreditTransferTransactionInformation;
procedure SetDbtrNm(const str: String);
function GetCtrlSum: Currency;
function GetCdtTrfTxInfEntry(const i: Integer): TCreditTransferTransactionInformation;
function GetCdtTrfTxInfCount: Integer;
public
constructor Create;
destructor Destroy; override;
property PmtInfId: String read fPmtInfId write fPmtInfId;
property PmtMtd: String read fPmtMtd write fPmtMtd;
property NbOfTxs: Integer read GetCdtTrfTxInfCount;
property CtrlSum: Currency read GetCtrlSum;
property PmtTpInfSvcLvlCd: String read fPmtTpInfSvcLvlCd write fPmtTpInfSvcLvlCd;
property PmtTpInfInstrPrty: String read fPmtTpInfInstrPrty write fPmtTpInfInstrPrty;
property ReqdExctnDt: TDateTime read fReqdExctnDt write fReqdExctnDt;
property DbtrNm: String read fDbtrNm write SetDbtrNm;
property DbtrAcct: TAccountIdentification read fDbtrAcct;
property DbtrAgt: TFinancialInstitution read fDbtrAgt;
property ChrgBr: String read fChrgBr write fChrgBr;
procedure AppendCdtTrfTxInfEntry(const transaction: TCreditTransferTransactionInformation);
property CdtTrfTxInfEntry[const i: Integer]: TCreditTransferTransactionInformation read GetCdtTrfTxInfEntry;
property CdtTrfTxInfCount: Integer read GetCdtTrfTxInfCount;
function Validate(const schema: String; const appendTo: TStringList = nil): TStringList;
procedure SaveToStream(const stream: TStream; const schema: String);
end;
TCreditTransferInitiation = class
private
fSchema: String; // ISO schema, e.g. "pain.001.002.03", empty means auto-select based on date
fGrpHdrMsgId: String; // group header: message identification
fGrpHdrCreDtTm: TDateTime; // group header: time of file creation
fGrpHdrInitgPtyName: String; // group header: initiator name
fPmtInf: array of TCreditTransferPaymentInformation;
function GetSchema: String;
procedure SetGrpHdrInitgPtyName(const str: String);
function GetGrpHdrNbOfTxs: Integer;
function GetPmtInfEntry(const i: Integer): TCreditTransferPaymentInformation;
function GetPmtInfCount: Integer;
public
constructor Create;
destructor Destroy; override;
property Schema: String read GetSchema write fSchema;
property GrpHdrMsgId: String read fGrpHdrMsgId write fGrpHdrMsgId;
property GrpHdrCreDtTm: TDateTime read fGrpHdrCreDtTm write fGrpHdrCreDtTm;
property GrpHdrNbOfTxs: Integer read GetGrpHdrNbOfTxs;
property GrpHdrInitgPtyName: String read fGrpHdrInitgPtyName write SetGrpHdrInitgPtyName;
procedure AppendPmtInfEntry(const instruction: TCreditTransferPaymentInformation);
property PmtInfEntry[const i: Integer]: TCreditTransferPaymentInformation read GetPmtInfEntry;
property PmtInfCount: Integer read GetPmtInfCount;
function Validate(const appendTo: TStringList = nil): TStringList;
procedure SaveToStream(const stream: TStream);
procedure SaveToDisk(const FileName: String);
end;
implementation
// TCreditTransferTransactionInformation
constructor TCreditTransferTransactionInformation.Create;
begin
inherited;
fPmtIdEndToEndId := END_TO_END_ID_NOTPROVIDED;
fInstdAmtCcy := CCY_EUR;
fCdtrAgt := TFinancialInstitution.Create;
fCdtrAcct := TAccountIdentification.Create;
end;
destructor TCreditTransferTransactionInformation.Destroy;
begin
FreeAndNil(fCdtrAgt);
FreeAndNil(fCdtrAcct);
inherited;
end;
procedure TCreditTransferTransactionInformation.SetCdtrNm(const str: String);
begin
fCdtrNm := SEPACleanString(str);
end;
procedure TCreditTransferTransactionInformation.SetRmtInfUstrd(const str: String);
begin
fRmtInfUstrd := SEPACleanString(str);
end;
function TCreditTransferTransactionInformation.Validate(const schema: String; const appendTo: TStringList = nil): TStringList;
begin
if appendTo <> nil then
Result := appendTo
else
Result := TStringList.Create;
// check for empty fields
if PmtIdEndToEndId = '' then
Result.Append(EMPTY_END_TO_END_ID);
if InstdAmtCcy = '' then
Result.Append(EMPTY_INSTD_AMT_CCY);
if CdtrNm = '' then
Result.Append(EMPTY_CDTR_NM);
if RmtInfUstrd = '' then
Result.Append(EMPTY_RMT_INF_USTRD);
// check for invalid fields
if not SEPACheckString(PmtIdEndToEndId, END_TO_END_ID_MAX_LEN) then
Result.Append(Format(INVALID_END_TO_END_ID, [PmtIdEndToEndId]));
if (InstdAmt <= 0.0) or not SEPACheckRounded(InstdAmt) then
Result.Append(Format(INVALID_INSTD_AMT, [SEPAFormatAmount(InstdAmt, 4)]));
if not SEPACheckString(CdtrNm, CDTR_NM_MAX_LEN) then
Result.Append(Format(INVALID_CDTR_NM, [CdtrNm]));
if not SEPACheckString(RmtInfUstrd, RMT_INF_USTRD_MAX_LEN) then
Result.Append(Format(INVALID_RMT_INF_USTRD, [RmtInfUstrd]));
// delegate validations where possible
// note: for IBAN-only, according to the specification the creditor agent
// has to be left out completely; not even NOTPROVIDED is allowed - yet, we
// handle this the same way and just do not write this <CdtrAgt> block
// to the file if no BIC is given (corresponds to NOTPROVIDED flag), see also
// method SaveToStream
CdtrAgt.Validate(schema, Result);
CdtrAcct.Validate(schema, Result);
// plausibility checks
if (CdtrAgt.OthrID = FIN_INSTN_NOTPROVIDED) and not SEPAIsGermanIBAN(CdtrAcct.IBAN) then
Result.Append(INVALID_IBAN_NOT_DE);
end;
procedure TCreditTransferTransactionInformation.SaveToStream(const stream: TStream; const schema: String);
begin
SEPAWriteLine(stream, '<CdtTrfTxInf>');
SEPAWriteLine(stream, '<PmtId><EndToEndId>'+SEPACleanString(PmtIdEndToEndId)+'</EndToEndId></PmtId>');
SEPAWriteLine(stream, '<Amt><InstdAmt Ccy="'+SEPACleanString(InstdAmtCcy)+'">'+SEPAFormatAmount(InstdAmt)+'</InstdAmt></Amt>');
if CdtrAgt.BIC <> '' then // note: do not write <CdtrAgt> block to the file if IBAN-only
begin // is required, see also comment in method Validate
SEPAWriteLine(stream, '<CdtrAgt>');
CdtrAgt.SaveToStream(stream, schema);
SEPAWriteLine(stream, '</CdtrAgt>');
end;
SEPAWriteLine(stream, '<Cdtr><Nm>'+SEPACleanString(CdtrNm, DBTR_NM_MAX_LEN)+'</Nm></Cdtr>');
SEPAWriteLine(stream, '<CdtrAcct>');
CdtrAcct.SaveToStream(stream, schema);
SEPAWriteLine(stream, '</CdtrAcct>');
SEPAWriteLine(stream, '<RmtInf><Ustrd>'+SEPACleanString(RmtInfUstrd, RMT_INF_USTRD_MAX_LEN)+'</Ustrd></RmtInf>');
SEPAWriteLine(stream, '</CdtTrfTxInf>');
end;
// TCreditTransferPaymentInformation
constructor TCreditTransferPaymentInformation.Create;
begin
inherited;
fPmtInfId := SEPAGenerateUUID;
fPmtMtd := PMT_MTD_CREDIT_TRANSFER;
fPmtTpInfSvcLvlCd := SEPA;
fChrgBr := CHRG_BR_SLEV;
fDbtrAcct := TAccountIdentification.Create;
fDbtrAgt := TFinancialInstitution.Create;
end;
destructor TCreditTransferPaymentInformation.Destroy;
var
i: Integer;
begin
FreeAndNil(fDbtrAcct);
FreeAndNil(fDbtrAgt);
for i := Low(fCdtTrfTxInf) to High(fCdtTrfTxInf) do
FreeAndNil(fCdtTrfTxInf[i]);
inherited;
end;
procedure TCreditTransferPaymentInformation.SetDbtrNm(const str: String);
begin
fDbtrNm := SEPACleanString(str);
end;
function TCreditTransferPaymentInformation.GetCtrlSum: Currency;
var
i: Integer;
begin
Result := 0.0;
for i := 0 to CdtTrfTxInfCount-1 do
Result := Result + CdtTrfTxInfEntry[i].InstdAmt;
end;
procedure TCreditTransferPaymentInformation.AppendCdtTrfTxInfEntry(const transaction: TCreditTransferTransactionInformation);
var
i: Integer;
begin
i := Length(fCdtTrfTxInf);
SetLength(fCdtTrfTxInf, i+1);
fCdtTrfTxInf[i] := transaction;
end;
function TCreditTransferPaymentInformation.GetCdtTrfTxInfEntry(const i: Integer): TCreditTransferTransactionInformation;
begin
Result := fCdtTrfTxInf[i];
end;
function TCreditTransferPaymentInformation.GetCdtTrfTxInfCount: Integer;
begin
Result := Length(fCdtTrfTxInf);
end;
function TCreditTransferPaymentInformation.Validate(const schema: String; const appendTo: TStringList = nil): TStringList;
var
i: Integer;
begin
if appendTo <> nil then
Result := appendTo
else
Result := TStringList.Create;
// check for empty fields
if PmtInfId = '' then
Result.Append(EMPTY_PMT_INF_ID);
if DbtrNm = '' then
Result.Append(EMPTY_DBTR_NM);
// check for invalid fields
if not SEPACheckString(PmtInfId, ID_MAX_LEN) then
Result.Append(Format(INVALID_PMT_INF_ID, [PmtInfId]));
if PmtMtd <> PMT_MTD_CREDIT_TRANSFER then
Result.Append(Format(INVALID_PMT_MTD, [PmtMtd]));
if Trunc(ReqdExctnDt) < Today then
Result.Append(Format(INVALID_REQD_EXCTN_DT, [DateToStr(ReqdExctnDt)]));
if PmtTpInfSvcLvlCd <> SEPA then
Result.Append(Format(INVALID_SVC_LVL_CD, [PmtTpInfSvcLvlCd]));
if (PmtTpInfInstrPrty <> '') and (PmtTpInfInstrPrty <> INSTR_PRTY_NORM) and (PmtTpInfInstrPrty <> INSTR_PRTY_HIGH) then
Result.Append(Format(INVALID_INSTR_PRTY, [PmtTpInfInstrPrty]));
if ChrgBr <> CHRG_BR_SLEV then
Result.Append(Format(INVALID_CHRG_BR, [ChrgBr]));
if not SEPACheckString(DbtrNm, DBTR_NM_MAX_LEN) then
Result.Append(Format(INVALID_DBTR_NM, [DbtrNm]));
// delegate validations where possible
DbtrAcct.Validate(schema, Result);
DbtrAgt.Validate(schema, Result);
for i := 0 to CdtTrfTxInfCount-1 do
CdtTrfTxInfEntry[i].Validate(schema, Result);
// plausibility checks
if not SEPAIsGermanIBAN(DbtrAcct.IBAN) then
Result.Append(INVALID_DBTR_ACCT_NOT_DE);
// note: number of objects in DrctDbtTxInf is not checked - if empty, then this
// object will be ignored by TCreditTransferInitiation; and TCreditTransferInitiation
// ensures in its validation that it has some transactions
end;
procedure TCreditTransferPaymentInformation.SaveToStream(const stream: TStream; const schema: String);
var
i: Integer;
begin
SEPAWriteLine(stream, '<PmtInf>');
SEPAWriteLine(stream, '<PmtInfId>'+SEPACleanString(PmtInfId)+'</PmtInfId>');
SEPAWriteLine(stream, '<PmtMtd>'+SEPACleanString(PmtMtd)+'</PmtMtd>');
SEPAWriteLine(stream, '<NbOfTxs>'+IntToStr(NbOfTxs)+'</NbOfTxs>');
SEPAWriteLine(stream, '<CtrlSum>'+SEPAFormatAmount(CtrlSum)+'</CtrlSum>');
SEPAWriteLine(stream, '<PmtTpInf>');
if PmtTpInfInstrPrty <> '' then
SEPAWriteLine(stream, '<InstrPrty>'+SEPACleanString(PmtTpInfInstrPrty)+'</InstrPrty>');
SEPAWriteLine(stream, '<SvcLvl><Cd>'+SEPACleanString(PmtTpInfSvcLvlCd)+'</Cd></SvcLvl>');
SEPAWriteLine(stream, '</PmtTpInf>');
SEPAWriteLine(stream, '<ReqdExctnDt>'+SEPAFormatDate(ReqdExctnDt)+'</ReqdExctnDt>');
SEPAWriteLine(stream, '<Dbtr><Nm>'+SEPACleanString(DbtrNm, DBTR_NM_MAX_LEN)+'</Nm></Dbtr>');
SEPAWriteLine(stream, '<DbtrAcct>');
DbtrAcct.SaveToStream(stream, schema);
SEPAWriteLine(stream, '</DbtrAcct>');
SEPAWriteLine(stream, '<DbtrAgt>');
DbtrAgt.SaveToStream(stream, schema);
SEPAWriteLine(stream, '</DbtrAgt>');
SEPAWriteLine(stream, '<ChrgBr>'+SEPACleanString(ChrgBr)+'</ChrgBr>');
for i := 0 to CdtTrfTxInfCount-1 do
CdtTrfTxInfEntry[i].SaveToStream(stream, schema);
SEPAWriteLine(stream, '</PmtInf>');
end;
// TCreditTransferInitiation
constructor TCreditTransferInitiation.Create;
begin
inherited;
fSchema := ''; // empty = auto-select
fGrpHdrMsgId := SEPAGenerateUUID;
fGrpHdrCreDtTm := Now;
end;
destructor TCreditTransferInitiation.Destroy;
var
i: Integer;
begin
for i := Low(fPmtInf) to High(fPmtInf) do
FreeAndNil(fPmtInf[i]);
inherited;
end;
function TCreditTransferInitiation.GetSchema: String;
begin
Result := fSchema;
if Result = '' then
Result := SCHEMA_PAIN_001_003_03
end;
procedure TCreditTransferInitiation.SetGrpHdrInitgPtyName(const str: String);
begin
fGrpHdrInitgPtyName := SEPACleanString(str);
end;
function TCreditTransferInitiation.GetGrpHdrNbOfTxs: Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to PmtInfCount-1 do
Inc(Result, PmtInfEntry[i].NbOfTxs);
end;
procedure TCreditTransferInitiation.AppendPmtInfEntry(const instruction: TCreditTransferPaymentInformation);
var
i: Integer;
begin
i := Length(fPmtInf);
SetLength(fPmtInf, i+1);
fPmtInf[i] := instruction;
end;
function TCreditTransferInitiation.GetPmtInfEntry(const i: Integer): TCreditTransferPaymentInformation;
begin
Result := fPmtInf[i];
end;
function TCreditTransferInitiation.GetPmtInfCount: Integer;
begin
Result := Length(fPmtInf);
end;
function TCreditTransferInitiation.Validate(const appendTo: TStringList = nil): TStringList;
var
i: Integer;
begin
if appendTo <> nil then
Result := appendTo
else
Result := TStringList.Create;
// check ISO schema
if (Schema <> SCHEMA_PAIN_001_002_03) and (Schema <> SCHEMA_PAIN_001_003_03) then
Result.Append(Format(UNKNOWN_SCHEMA, [Schema]));
// check for empty fields
if GrpHdrMsgId = '' then
Result.Append(EMPTY_GRP_HDR_MSG_ID);
if GrpHdrInitgPtyName = '' then
Result.Append(EMPTY_INITG_PTY_NAME);
// check for invalid fields
if not SEPACheckString(GrpHdrMsgId, ID_MAX_LEN) then
Result.Append(Format(INVALID_GRP_HDR_MSG_ID, [GrpHdrMsgId]));
if not SEPACheckString(GrpHdrInitgPtyName, INITG_PTY_NAME_MAX_LEN) then
Result.Append(Format(INVALID_INITG_PTY_NAME, [GrpHdrInitgPtyName]));
// delegate validations where possible
for i := 0 to PmtInfCount-1 do
PmtInfEntry[i].Validate(Schema, Result);
// plausibility checks
if GrpHdrNbOfTxs = 0 then
Result.Append(INVALID_NB_OF_TXS);
end;
procedure TCreditTransferInitiation.SaveToStream(const stream: TStream);
var
i: Integer;
begin
SEPAWriteLine(stream, '<?xml version="1.0" encoding="UTF-8"?>');
SEPAWriteLine(stream, '<Document xmlns="urn:iso:std:iso:20022:tech:xsd:'+Schema+'"'+
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'+
' xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:'+Schema+' '+Schema+'.xsd">');
SEPAWriteLine(stream, '<CstmrCdtTrfInitn>');
SEPAWriteLine(stream, '<GrpHdr>');
SEPAWriteLine(stream, '<MsgId>'+SEPACleanString(GrpHdrMsgId)+'</MsgId>');
SEPAWriteLine(stream, '<CreDtTm>'+SEPAFormatDateTime(GrpHdrCreDtTm)+'</CreDtTm>');
SEPAWriteLine(stream, '<NbOfTxs>'+IntToStr(GrpHdrNbOfTxs)+'</NbOfTxs>');
SEPAWriteLine(stream, '<InitgPty><Nm>'+SEPACleanString(GrpHdrInitgPtyName, INITG_PTY_NAME_MAX_LEN)+'</Nm></InitgPty>');
SEPAWriteLine(stream, '</GrpHdr>');
for i := 0 to PmtInfCount-1 do
if PmtInfEntry[i].NbOfTxs > 0 then
PmtInfEntry[i].SaveToStream(stream, Schema);
SEPAWriteLine(stream, '</CstmrCdtTrfInitn>');
SEPAWriteLine(stream, '</Document>');
end;
procedure TCreditTransferInitiation.SaveToDisk(const FileName: String);
var
stream: TMemoryStream;
begin
stream := TMemoryStream.Create;
try
SaveToStream(stream);
stream.SaveToFile(FileName);
finally
stream.Free;
end;
end;
end.