1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Fhp; |
4
|
|
|
|
5
|
|
|
use Fhp\Adapter\AdapterInterface; |
6
|
|
|
use Fhp\Adapter\Curl; |
7
|
|
|
use Fhp\DataTypes\Kik; |
8
|
|
|
use Fhp\DataTypes\Kti; |
9
|
|
|
use Fhp\DataTypes\Ktv; |
10
|
|
|
use Fhp\Dialog\Dialog; |
11
|
|
|
use Fhp\Message\AbstractMessage; |
12
|
|
|
use Fhp\Message\Message; |
13
|
|
|
use Fhp\Model\SEPAAccount; |
14
|
|
|
use Fhp\Response\GetAccounts; |
15
|
|
|
use Fhp\Response\GetSaldo; |
16
|
|
|
use Fhp\Response\GetSEPAAccounts; |
17
|
|
|
use Fhp\Response\GetStatementOfAccount; |
18
|
|
|
use Fhp\Segment\HKKAZ; |
19
|
|
|
use Fhp\Segment\HKSAL; |
20
|
|
|
use Fhp\Segment\HKSPA; |
21
|
|
|
use Psr\Log\LoggerInterface; |
22
|
|
|
use Psr\Log\NullLogger; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Class FinTs. |
26
|
|
|
* |
27
|
|
|
* @package Fhp |
28
|
|
|
*/ |
29
|
|
|
class FinTs |
30
|
|
|
{ |
31
|
|
|
const DEFAULT_COUNTRY_CODE = 280; |
32
|
|
|
|
33
|
|
|
/** @var LoggerInterface */ |
34
|
|
|
protected $logger; |
35
|
|
|
/** @var string */ |
36
|
|
|
protected $server; |
37
|
|
|
/** @var int */ |
38
|
|
|
protected $port; |
39
|
|
|
/** @var string */ |
40
|
|
|
protected $bankCode; |
41
|
|
|
/** @var string */ |
42
|
|
|
protected $username; |
43
|
|
|
/** @var string */ |
44
|
|
|
protected $pin; |
45
|
|
|
/** @var Connection */ |
46
|
|
|
protected $connection; |
47
|
|
|
/** @var AdapterInterface */ |
48
|
|
|
protected $adapter; |
49
|
|
|
/** @var int */ |
50
|
|
|
protected $systemId = 0; |
51
|
|
|
/** @var string */ |
52
|
|
|
protected $bankName; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* FinTs constructor. |
56
|
|
|
* @param string $server |
57
|
|
|
* @param int $port |
58
|
|
|
* @param string $bankCode |
59
|
|
|
* @param string $username |
60
|
|
|
* @param string $pin |
61
|
|
|
* @param LoggerInterface|null $logger |
62
|
|
|
*/ |
63
|
|
|
public function __construct( |
64
|
|
|
$server, |
65
|
|
|
$port, |
66
|
|
|
$bankCode, |
67
|
|
|
$username, |
68
|
|
|
$pin, |
69
|
|
|
LoggerInterface $logger = null |
70
|
|
|
) { |
71
|
|
|
$this->server = $server; |
72
|
|
|
$this->port = $port; |
73
|
|
|
$this->logger = null == $logger ? new NullLogger() : $logger; |
74
|
|
|
|
75
|
|
|
// escaping of bank code not really needed here as it should |
76
|
|
|
// never have special chars. But we just do it to ensure |
77
|
|
|
// that the request will not get messed up and the user |
78
|
|
|
// can receive a valid error response from the HBCI server. |
79
|
|
|
$this->bankCode = $this->escapeString($bankCode); |
80
|
|
|
|
81
|
|
|
// Here, escaping is needed for usernames or pins with |
82
|
|
|
// HBCI special chars. |
83
|
|
|
$this->username = $this->escapeString($username); |
84
|
|
|
$this->pin = $this->escapeString($pin); |
85
|
|
|
|
86
|
|
|
$this->adapter = new Curl($this->server, $this->port); |
87
|
|
|
$this->connection = new Connection($this->adapter); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Sets the adapter to use. |
92
|
|
|
* |
93
|
|
|
* @param AdapterInterface $adapter |
94
|
|
|
*/ |
95
|
|
|
public function setAdapter(AdapterInterface $adapter) |
96
|
|
|
{ |
97
|
|
|
$this->adapter = $adapter; |
98
|
|
|
$this->connection = new Connection($this->adapter); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Gets array of all accounts. |
103
|
|
|
* |
104
|
|
|
* @return Model\Account[] |
105
|
|
|
*/ |
106
|
|
|
public function getAccounts() |
107
|
|
|
{ |
108
|
|
|
$dialog = $this->getDialog(); |
109
|
|
|
$result = $dialog->syncDialog(); |
110
|
|
|
$this->bankName = $dialog->getBankName(); |
111
|
|
|
$accounts = new GetAccounts($result); |
112
|
|
|
|
113
|
|
|
return $accounts->getAccountsArray(); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Gets array of all SEPA Accounts. |
118
|
|
|
* |
119
|
|
|
* @return Model\SEPAAccount[] |
120
|
|
|
* @throws Adapter\Exception\AdapterException |
121
|
|
|
* @throws Adapter\Exception\CurlException |
122
|
|
|
*/ |
123
|
|
|
public function getSEPAAccounts() |
124
|
|
|
{ |
125
|
|
|
$dialog = $this->getDialog(); |
126
|
|
|
$dialog->syncDialog(); |
127
|
|
|
$dialog->initDialog(); |
128
|
|
|
|
129
|
|
|
$message = $this->getNewMessage( |
130
|
|
|
$dialog, |
131
|
|
|
array(new HKSPA(3)), |
132
|
|
|
array(AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms()) |
133
|
|
|
); |
134
|
|
|
|
135
|
|
|
$result = $dialog->sendMessage($message); |
136
|
|
|
$dialog->endDialog(); |
137
|
|
|
$sepaAccounts = new GetSEPAAccounts($result->rawResponse); |
138
|
|
|
|
139
|
|
|
return $sepaAccounts->getSEPAAccountsArray(); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Gets the bank name. |
144
|
|
|
* |
145
|
|
|
* @return string |
146
|
|
|
*/ |
147
|
|
|
public function getBankName() |
148
|
|
|
{ |
149
|
|
|
if (null == $this->bankName) { |
150
|
|
|
$this->getDialog()->syncDialog(); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return $this->bankName; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Gets statement of account. |
158
|
|
|
* |
159
|
|
|
* @param SEPAAccount $account |
160
|
|
|
* @param \DateTime $from |
161
|
|
|
* @param \DateTime $to |
162
|
|
|
* @return Model\StatementOfAccount\StatementOfAccount|null |
163
|
|
|
* @throws \Exception |
164
|
|
|
*/ |
165
|
|
|
public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to) |
166
|
|
|
{ |
167
|
|
|
$responses = array(); |
168
|
|
|
|
169
|
|
|
$this->logger->info('Start fetching statement of account results'); |
170
|
|
|
$this->logger->info('Start date: ' . $from->format('Y-m-d')); |
171
|
|
|
$this->logger->info('End date : ' . $to->format('Y-m-d')); |
172
|
|
|
|
173
|
|
|
$dialog = $this->getDialog(); |
174
|
|
|
$dialog->syncDialog(); |
175
|
|
|
$dialog->initDialog(); |
176
|
|
|
|
177
|
|
|
$message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); |
178
|
|
|
$response = $dialog->sendMessage($message); |
179
|
|
|
$touchdowns = $response->getTouchdowns($message); |
180
|
|
|
$soaResponse = new GetStatementOfAccount($response->rawResponse); |
181
|
|
|
$responses[] = $soaResponse->getRawMt940(); |
182
|
|
|
|
183
|
|
|
$touchdownCounter = 1; |
184
|
|
|
while (isset($touchdowns[HKKAZ::NAME])) { |
185
|
|
|
$this->logger->info('Fetching more statement of account results (' . $touchdownCounter++ . ') ...'); |
186
|
|
|
$message = $this->createStateOfAccountMessage( |
187
|
|
|
$dialog, |
188
|
|
|
$account, |
189
|
|
|
$from, |
190
|
|
|
$to, |
191
|
|
|
$touchdowns[HKKAZ::NAME] |
192
|
|
|
); |
193
|
|
|
|
194
|
|
|
$r = $dialog->sendMessage($message); |
195
|
|
|
$touchdowns = $r->getTouchDowns($message); |
196
|
|
|
$soaResponse = new GetStatementOfAccount($r->rawResponse); |
197
|
|
|
$responses[] = $soaResponse->getRawMt940(); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
$this->logger->info('Fetching of ' . $touchdownCounter . ' pages done.'); |
201
|
|
|
$this->logger->debug('HKKAZ response:'); |
202
|
|
|
|
203
|
|
|
$dialog->endDialog(); |
204
|
|
|
|
205
|
|
|
return GetStatementOfAccount::createModelFromRawMt940(implode('', $responses)); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Helper method to create a "Statement of Account Message". |
210
|
|
|
* |
211
|
|
|
* @param Dialog $dialog |
212
|
|
|
* @param SEPAAccount $account |
213
|
|
|
* @param \DateTime $from |
214
|
|
|
* @param \DateTime $to |
215
|
|
|
* @param string|null $touchdown |
216
|
|
|
* @return Message |
217
|
|
|
* @throws \Exception |
218
|
|
|
*/ |
219
|
|
|
protected function createStateOfAccountMessage( |
220
|
|
|
Dialog $dialog, |
221
|
|
|
SepaAccount $account, |
222
|
|
|
\DateTime $from, |
223
|
|
|
\DateTime $to, |
224
|
|
|
$touchdown = null |
225
|
|
|
) { |
226
|
|
|
// version 4, 5, 6, 7 |
|
|
|
|
227
|
|
|
|
228
|
|
|
// version 5 |
229
|
|
|
/* |
230
|
|
|
1 Segmentkopf DEG M 1 |
231
|
|
|
2 Kontoverbindung Auftraggeber DEG ktv # M 1 |
232
|
|
|
3 Alle Konten DE jn # M 1 |
233
|
|
|
4 Von Datum DE dat # K 1 |
234
|
|
|
5 Bis Datum DE dat # K 1 |
235
|
|
|
6 Maximale Anzahl Einträge DE num ..4 K 1 >0 |
236
|
|
|
7 Aufsetzpunkt DE an ..35 K 1 |
237
|
|
|
*/ |
238
|
|
|
|
239
|
|
|
// version 6 |
240
|
|
|
/* |
241
|
|
|
1 Segmentkopf 1 DEG M 1 |
242
|
|
|
2 Kontoverbindung Auftraggeber 2 DEG ktv # M 1 |
243
|
|
|
3 Alle Konten 1 DE jn # M 1 |
244
|
|
|
4 Von Datum 1 DE dat # O 1 |
245
|
|
|
5 Bis Datum 1 DE dat # O 1 |
246
|
|
|
6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 |
247
|
|
|
7 Aufsetzpunkt 1 DE an ..35 C 1 |
248
|
|
|
*/ |
249
|
|
|
|
250
|
|
|
// version 7 |
251
|
|
|
/* |
252
|
|
|
1 Segmentkopf 1 DEG M 1 |
253
|
|
|
2 Kontoverbindung international 1 DEG kti # M 1 |
254
|
|
|
3 Alle Konten 1 DE jn # M 1 |
255
|
|
|
4 Von Datum 1 DE dat # O 1 |
256
|
|
|
5 Bis Datum 1 DE dat # O 1 |
257
|
|
|
6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 |
258
|
|
|
7 Aufsetzpunkt 1 DE an ..35 C 1 |
259
|
|
|
*/ |
260
|
|
|
|
261
|
|
|
switch ($dialog->getHkkazMaxVersion()) { |
262
|
|
|
case 4: |
263
|
|
|
case 5: |
264
|
|
|
$konto = new Deg(); |
265
|
|
|
$konto->addDataElement($account->getAccountNumber()); |
266
|
|
|
$konto->addDataElement($account->getSubAccount()); |
267
|
|
|
$konto->addDataElement(static::DEFAULT_COUNTRY_CODE); |
268
|
|
|
$konto->addDataElement($account->getBlz()); |
269
|
|
|
break; |
270
|
|
View Code Duplication |
case 6: |
|
|
|
|
271
|
|
|
$konto = new Ktv( |
272
|
|
|
$account->getAccountNumber(), |
273
|
|
|
$account->getSubAccount(), |
274
|
|
|
new Kik(280, $account->getBlz()) |
275
|
|
|
); |
276
|
|
|
break; |
277
|
|
View Code Duplication |
case 7: |
|
|
|
|
278
|
|
|
$konto = new Kti( |
279
|
|
|
$account->getIban(), |
280
|
|
|
$account->getBic(), |
281
|
|
|
$account->getAccountNumber(), |
282
|
|
|
$account->getSubAccount(), |
283
|
|
|
new Kik(280, $account->getBlz()) |
284
|
|
|
); |
285
|
|
|
break; |
286
|
|
|
default: |
287
|
|
|
throw new \Exception('Unsupported HKKAZ version: ' . $dialog->getHkkazMaxVersion()); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
$message = $this->getNewMessage( |
291
|
|
|
$dialog, |
292
|
|
|
array( |
293
|
|
|
new HKKAZ( |
294
|
|
|
$dialog->getHkkazMaxVersion(), |
295
|
|
|
3, |
296
|
|
|
$konto, |
297
|
|
|
HKKAZ::ALL_ACCOUNTS_N, |
298
|
|
|
$from, |
299
|
|
|
$to, |
300
|
|
|
$touchdown |
301
|
|
|
) |
302
|
|
|
), |
303
|
|
|
array(AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms()) |
304
|
|
|
); |
305
|
|
|
|
306
|
|
|
return $message; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Gets the saldo of given SEPAAccount. |
311
|
|
|
* |
312
|
|
|
* @param SEPAAccount $account |
313
|
|
|
* @return Model\Saldo|null |
314
|
|
|
* @throws Adapter\Exception\AdapterException |
315
|
|
|
* @throws Adapter\Exception\CurlException |
316
|
|
|
* @throws \Exception |
317
|
|
|
*/ |
318
|
|
|
public function getSaldo(SEPAAccount $account) |
319
|
|
|
{ |
320
|
|
|
$dialog = $this->getDialog(); |
321
|
|
|
$dialog->syncDialog(); |
322
|
|
|
$dialog->initDialog(); |
323
|
|
|
|
324
|
|
|
switch ((int) $dialog->getHksalMaxVersion()) { |
325
|
|
|
case 4: |
326
|
|
|
case 5: |
327
|
|
|
$hksalAccount = new Deg( |
328
|
|
|
$account->getAccountNumber(), |
|
|
|
|
329
|
|
|
$account->getSubAccount(), |
330
|
|
|
static::DEFAULT_COUNTRY_CODE, $account->getBlz() |
331
|
|
|
); |
332
|
|
|
$hksalAccount->addDataElement($account->getAccountNumber()); |
333
|
|
|
$hksalAccount->addDataElement($account->getSubAccount()); |
334
|
|
|
$hksalAccount->addDataElement(static::DEFAULT_COUNTRY_CODE); |
335
|
|
|
$hksalAccount->addDataElement($account->getBlz()); |
336
|
|
|
break; |
337
|
|
View Code Duplication |
case 6: |
|
|
|
|
338
|
|
|
$hksalAccount = new Ktv( |
339
|
|
|
$account->getAccountNumber(), |
340
|
|
|
$account->getSubAccount(), |
341
|
|
|
new Kik(280, $account->getBlz()) |
342
|
|
|
); |
343
|
|
|
break; |
344
|
|
View Code Duplication |
case 7: |
|
|
|
|
345
|
|
|
$hksalAccount = new Kti( |
346
|
|
|
$account->getIban(), |
347
|
|
|
$account->getBic(), |
348
|
|
|
$account->getAccountNumber(), |
349
|
|
|
$account->getSubAccount(), |
350
|
|
|
new Kik(280, $account->getBlz()) |
351
|
|
|
); |
352
|
|
|
break; |
353
|
|
|
default: |
354
|
|
|
throw new \Exception('Unsupported HKSAL version: ' . $dialog->getHksalMaxVersion()); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
$message = new Message( |
358
|
|
|
$this->bankCode, |
359
|
|
|
$this->username, |
360
|
|
|
$this->pin, |
361
|
|
|
$dialog->getSystemId(), |
362
|
|
|
$dialog->getDialogId(), |
363
|
|
|
$dialog->getMessageNumber(), |
364
|
|
|
array( |
365
|
|
|
new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N) |
366
|
|
|
), |
367
|
|
|
array( |
368
|
|
|
AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() |
369
|
|
|
) |
370
|
|
|
); |
371
|
|
|
|
372
|
|
|
$response = $dialog->sendMessage($message); |
373
|
|
|
$response = new GetSaldo($response->rawResponse); |
374
|
|
|
|
375
|
|
|
return $response->getSaldoModel(); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Helper method to retrieve a pre configured message object. |
380
|
|
|
* Factory for poor people :) |
381
|
|
|
* |
382
|
|
|
* @param Dialog $dialog |
383
|
|
|
* @param array $segments |
384
|
|
|
* @param array $options |
385
|
|
|
* @return Message |
386
|
|
|
*/ |
387
|
|
|
protected function getNewMessage(Dialog $dialog, array $segments, array $options) |
388
|
|
|
{ |
389
|
|
|
return new Message( |
390
|
|
|
$this->bankCode, |
391
|
|
|
$this->username, |
392
|
|
|
$this->pin, |
393
|
|
|
$dialog->getSystemId(), |
394
|
|
|
$dialog->getDialogId(), |
395
|
|
|
$dialog->getMessageNumber(), |
396
|
|
|
$segments, |
397
|
|
|
$options |
398
|
|
|
); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Helper method to retrieve a pre configured dialog object. |
403
|
|
|
* Factory for poor people :) |
404
|
|
|
* |
405
|
|
|
* @return Dialog |
406
|
|
|
*/ |
407
|
|
|
protected function getDialog() |
408
|
|
|
{ |
409
|
|
|
return new Dialog( |
410
|
|
|
$this->connection, |
411
|
|
|
$this->bankCode, |
412
|
|
|
$this->username, |
413
|
|
|
$this->pin, |
414
|
|
|
$this->systemId, |
415
|
|
|
$this->logger |
416
|
|
|
); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Needed for escaping userdata. |
421
|
|
|
* HBCI escape char is "?" |
422
|
|
|
* |
423
|
|
|
* @param string $string |
424
|
|
|
* @return string |
425
|
|
|
*/ |
426
|
10 |
|
protected function escapeString($string) |
427
|
|
|
{ |
428
|
10 |
|
return str_replace( |
429
|
10 |
|
array('?', '@', ':', '+', '\''), |
430
|
10 |
|
array('??', '?@', '?:', '?+', '?\''), |
431
|
|
|
$string |
432
|
10 |
|
); |
433
|
|
|
} |
434
|
|
|
} |
435
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.