Formatting
[memberdb.git] / js / sepa-CORE.js
1 // stolen from https://gist.github.com/panzi/1857360
2
3 var XML_CHAR_MAP = {
4     '<': '&lt;',
5     '>': '&gt;',
6     '&': '&amp;',
7     '"': '&quot;',
8     "'": '&apos;'
9 };
10
11 function escapeXml(s) {
12     return s.replace(/[<>&"']/g, function (ch) {
13         return XML_CHAR_MAP[ch];
14     });
15 }
16
17 //
18 // PLEASE DO NOT HARM YOURSELF: DO NOT TRY TO IMPROVE THIS QUICK AND DIRTY HACK!!!
19 // PLEASE CONSIDER A PROPER REIMPLEMENTATION OF pain.008.003.02 IN JS INSTEAD.
20 //
21
22 // TODO: check for valid characters instead of just escapeXml!
23
24 var SEPACORE = {
25
26     creditorname: null,
27     creditoridentifier: null,
28     creditoriban: null,
29     creditorbic: null,
30
31     directdebittxs: {'FRST': [], 'RCUR': []},
32     directdebittxssums: {'FRST': 0, 'RCUR': 0},
33
34     creationdate: new Date(),
35     // only one collectiondate for FRST & RCUR is supported
36     collectiondate: null,
37
38     errormsg: '',
39
40     init: function (collectiondate, creditoridentifier, creditorname, creditoriban, creditorbic) {
41
42         var errors = [];
43         var argscntOK = true;
44
45         if (arguments.length != 4 && arguments.length != 5) {
46             this.errors.push('initSEPACORE mit falscher Parameteranzahl aufgerufen (Soll: 4 oder 5; Ist: ' + arguments.length + ').');
47             argscntOK = false;
48         }
49
50         if (argscntOK && (creditorname.length == 0 || creditorname.length > 70)) {
51             errors.push('Name des Zahlungsempfängers muss zwischen 1 und 70 Zeichen lang sein (nicht ' + creditorname.length + ').');
52         }
53         this.creditorname = creditorname;
54
55         if (argscntOK && (creditoriban.length < 15 || creditoriban.length > 32)) {
56             errors.push('IBAN des Zahlungsempfängers muss zwischen 15 und 32 Zeichen lang sein (nicht ' + creditoriban.length + ').');
57         }
58         this.creditoriban = creditoriban;
59
60         if (argscntOK && creditorbic != null && creditorbic.length != 8 && creditorbic.length != 11) {
61             errors.push('BIC des Zahlungsempfängers muss 8 oder 11 Zeichen lang oder nicht gesetzt sein (nicht ' + creditorbic.length + ').');
62         }
63         this.creditorbic = creditorbic;
64
65         if (argscntOK && (creditoridentifier.length == 0 || creditoridentifier.length > 35)) {
66             errors.push('Gläubiger-ID für den Zahlungspflichtigen muss zwischen 1 und 35 Zeichen lang sein (nicht ' + creditormndtid.length + ').');
67         }
68         this.creditoridentifier = creditoridentifier;
69
70         this.collectiondate = collectiondate;
71
72
73         this.directdebittxs = {'FRST': [], 'RCUR': []};
74         this.directdebittxssums = {'FRST': 0, 'RCUR': 0};
75
76         this.creationdate = new Date(),
77             this.errormsg = '';
78
79         return true;
80     },
81
82     addDDTx: function (ddtype, debtorname, debtoriban, debtorbic, debtormndtid, debtormndtdate, amountcent, purpose, e2eid) {
83
84         var errors = [];
85         var argscntOK = true;
86
87         if (arguments.length != 9 && arguments.length != 8) {
88             this.errors.push('addDDTx mit falscher Parameteranzahl aufgerufen (Soll: 8 oder 9; Ist: ' + arguments.length + ').');
89             argscntOK = false;
90         }
91
92         if (argscntOK && ddtype != 'FRST' && ddtype != 'RCUR') {
93             errors.push('Sequenztyp (ddtype) muss FRST oder RCUR sein.');
94         }
95
96         if (argscntOK && (debtorname.length == 0 || debtorname.length > 70)) {
97             errors.push('Name des Zahlungspflichtigen muss zwischen 1 und 70 Zeichen lang sein (nicht ' + debtorname.length + ').');
98         }
99
100         if (argscntOK && (debtoriban.length < 15 || debtoriban.length > 32)) {
101             errors.push('IBAN des Zahlungspflichtigen muss zwischen 15 und 32 Zeichen lang sein (nicht ' + debtoriban.length + ').');
102         }
103
104         if (argscntOK && debtorbic != null && debtorbic.length != 8 && debtorbic.length != 11) {
105             errors.push('BIC des Zahlungspflichtigen muss 8 oder 11 Zeichen lang oder nicht gesetzt sein (nicht ' + debtorbic.length + ').');
106         }
107
108         if (argscntOK && (debtormndtid.length == 0 || debtormndtid.length > 35)) {
109             errors.push('Mandatsreferenz für den Zahlungspflichtigen muss zwischen 1 und 35 Zeichen lang sein (nicht ' + debtormndtid.length + ').');
110         }
111
112         if (argscntOK && debtormndtdate.length != 10) {
113             errors.push('Datum der Mandatsunterschrift muss 10 Zeichen lang sein (nicht ' + debtormndtdate.length + ').');
114         }
115
116         if (!/^[-]?\d{4}-(0[1-9]|1[0-2])-(0[0-9]|[1-5][0-9]|60)[Z\-]?\d*:?\d*$/.test(debtormndtdate)) {
117             errors.push('Datum der Mandatsunterschrift ist nicht in Ordnung (' + debtormndtdate + ').');
118         }
119
120         if (argscntOK && (isNaN(amountcent) || amountcent < 0)) {
121             errors.push('Betrag muss eine Zahl und darf nicht negativ (' + amountcent + ' cent) sein.');
122         }
123         amountcent = parseInt(amountcent);
124
125         purpose = purpose || '';
126         if (argscntOK && purpose.length > 140) {
127             errors.push('Verwendungszweck muss zwischen 0 und 140 Zeichen lang sein (nicht ' + purpose.length + ').');
128         }
129
130         e2eid = e2eid || 'NOTPROVIDED';
131         if (argscntOK && purpose.length > 35) {
132             errors.push('End-to-End ID muss zwischen 0 und 35 Zeichen lang sein (nicht ' + purpose.length + ').');
133         }
134         if (e2eid == '') {
135             e2eid = 'NOTPROVIDED';
136         }
137
138         if (debtorbic != null) {
139             bicstr = '<DbtrAgt><FinInstnId><BIC>' + escapeXml(debtorbic) + '</BIC></FinInstnId></DbtrAgt>';
140         } else {
141             bicstr = '<!-- no BIC for DbtrAgt supplied -->';
142         }
143
144         if (purpose != '') {
145             purposestr = '<RmtInf><Ustrd>' + escapeXml(purpose) + '</Ustrd></RmtInf>';
146         } else {
147             purposestr = '<!-- no Ustrd for RmtInf supplied -->';
148         }
149
150         if (errors.length == 0) {
151             this.directdebittxs[ddtype].push([
152                 '      <DrctDbtTxInf>',
153                 '        <PmtId><EndToEndId>' + escapeXml(e2eid) + '</EndToEndId></PmtId>',
154                 '        <InstdAmt Ccy="EUR">' + centToEur(amountcent) + '</InstdAmt>',
155                 '        <DrctDbtTx><MndtRltdInf>',
156                 '          <MndtId>' + escapeXml(debtormndtid) + '</MndtId>',
157                 '          <DtOfSgntr>' + escapeXml(debtormndtdate) + '</DtOfSgntr>',
158                 '          <AmdmntInd>false</AmdmntInd>',
159                 '        </MndtRltdInf></DrctDbtTx>',
160                 '        ' + bicstr,
161                 '        <Dbtr><Nm>' + escapeXml(debtorname) + '</Nm></Dbtr>',
162                 '        <DbtrAcct><Id><IBAN>' + escapeXml(debtoriban) + '</IBAN></Id></DbtrAcct>',
163                 '        ' + purposestr,
164                 '      </DrctDbtTxInf>'].join('\n'));
165             this.directdebittxssums[ddtype] += amountcent;
166             return true;
167         }
168
169         this.errormsg = 'Fehler beim Hinzufügen eines Empfängers:\n';
170         for (var i = 0; i < errors.length; i++) {
171             this.errormsg += ' - ' + errors[i] + '\n';
172         }
173         return false;
174     },
175
176     getGroupHeaderBlock: function (ddtype) {
177
178         if (ddtype != 'FRST' && ddtype != 'RCUR') {
179             this.errormsg += ' - getGroupHeaderBlock: Sequenztyp (ddtype) muss FRST oder RCUR sein.\n';
180             return;
181         }
182
183         createdatestr = [
184             this.creationdate.getUTCFullYear(), '-',
185             str_pad_left(this.creationdate.getUTCMonth() + 1, 2, '0'), '-',
186             str_pad_left(this.creationdate.getUTCDate(), 2, '0'), 'T',
187             str_pad_left(this.creationdate.getUTCHours(), 2, '0'), ':',
188             str_pad_left(this.creationdate.getUTCMinutes(), 2, '0'), ':',
189             str_pad_left(this.creationdate.getUTCSeconds(), 2, '0'), '.000Z'].join('');
190
191         return [
192             '    <GrpHdr>',
193             '      <MsgId>/V:1/MSG:' + parseInt(this.creationdate.getTime() / 1000) + '/S:' + ddtype[0] + '/</MsgId>',
194             '      <CreDtTm>' + createdatestr + '</CreDtTm>',
195             '      <NbOfTxs>' + this.directdebittxs[ddtype].length + '</NbOfTxs>',
196             '      <InitgPty><Nm>' + escapeXml(this.creditorname) + '</Nm></InitgPty>',
197             '    </GrpHdr>'].join('\n');
198     },
199
200     getPaymentInformationHeaderBlock: function (ddtype) {
201
202         if (ddtype != 'FRST' && ddtype != 'RCUR') {
203             this.errormsg += ' - getPaymentInformationHeaderBlock: Sequenztyp (ddtype) muss FRST oder RCUR sein.\n';
204             return;
205         }
206
207         // Localtime or UTC???
208         collectiondatestr = [this.collectiondate.getFullYear(),
209             str_pad_left(this.collectiondate.getMonth() + 1, 2, '0'),
210             str_pad_left(this.collectiondate.getDate(), 2, '0')
211         ].join('-');
212
213         return [
214             '      <PmtInfId>/V:1/PMT:' + parseInt(this.creationdate.getTime() / 1000) + '/S:' + ddtype[0] + '/</PmtInfId>',
215             '      <PmtMtd>DD</PmtMtd>',
216             '      <NbOfTxs>' + this.directdebittxs[ddtype].length + '</NbOfTxs>',
217             '      <CtrlSum>' + centToEur(this.directdebittxssums[ddtype]) + '</CtrlSum>',
218             '      <PmtTpInf><SvcLvl><Cd>SEPA</Cd></SvcLvl>',
219             '        <LclInstrm><Cd>CORE</Cd></LclInstrm>',
220             '        <SeqTp>' + ddtype + '</SeqTp>',
221             '      </PmtTpInf>',
222             '      <ReqdColltnDt>' + collectiondatestr + '</ReqdColltnDt>'
223         ].join('\n');
224     },
225
226     getCreditorBlock: function () {
227         if (this.creditorbic != null) {
228             bicstr = '<CdtrAgt><FinInstnId><BIC>' + escapeXml(this.creditorbic) + '</BIC></FinInstnId></CdtrAgt>';
229         } else {
230             bicstr = '<!-- no BIC for CdtrAgt supplied -->';
231         }
232         return [
233             '      <Cdtr><Nm>' + escapeXml(this.creditorname) + '</Nm> </Cdtr>',
234             '      <CdtrAcct><Id><IBAN>' + escapeXml(this.creditoriban) + '</IBAN></Id></CdtrAcct>',
235             '      ' + bicstr,
236             '      <ChrgBr>SLEV</ChrgBr>',
237             '      <CdtrSchmeId><Id><PrvtId><Othr>',
238             '        <Id>' + escapeXml(this.creditoridentifier) + '</Id>',
239             '        <SchmeNm><Prtry>SEPA</Prtry></SchmeNm>',
240             '      </Othr></PrvtId></Id></CdtrSchmeId>'
241         ].join('\n');
242     },
243
244     getDirectDebitBlock: function (ddtype) {
245         if (ddtype != 'FRST' && ddtype != 'RCUR') {
246             this.errormsg += ' - getDirectDebitBlock: Sequenztyp (ddtype) muss FRST oder RCUR sein.\n';
247             return;
248         }
249         return this.directdebittxs[ddtype].join('\n');
250     },
251
252     getXMLContent: function (ddtype) {
253         if (ddtype != 'FRST' && ddtype != 'RCUR') {
254             this.errormsg += ' - getXMLContent: Sequenztyp (ddtype) muss FRST oder RCUR sein.\n';
255             return;
256         }
257         if (this.directdebittxs[ddtype].length == 0) {
258             return '<!-- Keine ' + ((ddtype == 'FRST') ? 'SEPA Ersteinzüge' : 'wiederkehrenden SEPA Einzüge') + ' -->';
259         } else {
260             return [
261                 '<?xml version="1.0" encoding="UTF-8"?>',
262                 '<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.008.003.02" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.008.003.02 pain.008.003.02.xsd">',
263                 '  <CstmrDrctDbtInitn>',
264                 this.getGroupHeaderBlock(ddtype),
265                 '    <PmtInf>',
266                 this.getPaymentInformationHeaderBlock(ddtype),
267                 this.getCreditorBlock(),
268                 this.getDirectDebitBlock(ddtype),
269                 '    </PmtInf>',
270                 '  </CstmrDrctDbtInitn>',
271                 '</Document>'].join('\n');
272         }
273     },
274
275 }