Merge pull request 'Feature: FinTS-TAN-Verfahren per Verbindung konfigurierbar + Decoupled-Polling-Fix' (#88) from fds2610-bot/Steuerkram:main into main

Reviewed-on: https://codeberg.org/fds2610/Steuerkram/pulls/88
This commit is contained in:
fds2610 2026-05-23 14:32:30 +02:00
commit 5c067e3ce8
5 changed files with 43 additions and 10 deletions

View file

@ -0,0 +1,3 @@
ALTER TABLE bank_sources
ADD COLUMN IF NOT EXISTS tan_mode_id INT NULL DEFAULT NULL
COMMENT 'Bevorzugte FinTS-TAN-Verfahren-ID (z.B. 913 für chipTAN QR); NULL = erstes verfügbares';

View file

@ -443,6 +443,7 @@ class BankController extends BaseController
'fints_url' => $isFints ? (trim($_POST['fints_url'] ?? '') ?: null) : null, 'fints_url' => $isFints ? (trim($_POST['fints_url'] ?? '') ?: null) : null,
'benutzerkennung' => $isFints ? (trim($_POST['benutzerkennung'] ?? '') ?: null) : null, 'benutzerkennung' => $isFints ? (trim($_POST['benutzerkennung'] ?? '') ?: null) : null,
'pin' => $isFints ? (trim($_POST['pin'] ?? '') ?: null) : null, 'pin' => $isFints ? (trim($_POST['pin'] ?? '') ?: null) : null,
'tan_mode_id' => $isFints ? (($_POST['tan_mode_id'] ?? '') !== '' ? (int)$_POST['tan_mode_id'] : null) : null,
'account_filter' => null, // Konten werden beim ersten Abruf ausgewählt 'account_filter' => null, // Konten werden beim ersten Abruf ausgewählt
]); ]);
@ -547,6 +548,7 @@ class BankController extends BaseController
'fints_url' => trim($_POST['fints_url'] ?? '') ?: null, 'fints_url' => trim($_POST['fints_url'] ?? '') ?: null,
'benutzerkennung' => trim($_POST['benutzerkennung'] ?? '') ?: null, 'benutzerkennung' => trim($_POST['benutzerkennung'] ?? '') ?: null,
'pin' => $newPin !== '' ? $newPin : $source['pin'], 'pin' => $newPin !== '' ? $newPin : $source['pin'],
'tan_mode_id' => ($_POST['tan_mode_id'] ?? '') !== '' ? (int)$_POST['tan_mode_id'] : null,
]; ];
$filterUpdate = BankSource::computeFilterFromPost( $filterUpdate = BankSource::computeFilterFromPost(
$source['account_filter'] ?? null, $source['account_filter'] ?? null,

View file

@ -222,7 +222,7 @@ class BankSource
'buchungstext_template_debit', 'buchungstext_template_credit', 'buchungstext_template_debit', 'buchungstext_template_credit',
'sandbox', 'last_fetched_at', 'fetch_range', 'sandbox', 'last_fetched_at', 'fetch_range',
'app_name', 'client_id_enc', 'secret_enc', 'app_name', 'client_id_enc', 'secret_enc',
'blz', 'fints_url', 'benutzerkennung', 'pin_enc', 'account_filter', 'blz', 'fints_url', 'benutzerkennung', 'pin_enc', 'account_filter', 'tan_mode_id',
]; ];
$out = []; $out = [];

View file

@ -104,8 +104,25 @@ class FinTsFetchService
$log(sprintf(' [%d] %s (ID %d)', $idx, $mode->getName(), $mode->getId())); $log(sprintf(' [%d] %s (ID %d)', $idx, $mode->getName(), $mode->getId()));
} }
$selectedTanMode = reset($tanModes); $preferredId = isset($this->source['tan_mode_id']) && $this->source['tan_mode_id'] !== null
$log('Auswahl: ' . $selectedTanMode->getName() . ' (erstes verfügbares Verfahren)'); ? (int)$this->source['tan_mode_id']
: null;
$selectedTanMode = null;
if ($preferredId !== null) {
foreach ($tanModes as $mode) {
if ($mode->getId() === $preferredId) {
$selectedTanMode = $mode;
break;
}
}
if ($selectedTanMode === null) {
$log('WARNUNG: TAN-Verfahren ID ' . $preferredId . ' nicht gefunden – nutze erstes verfügbares.');
}
}
if ($selectedTanMode === null) {
$selectedTanMode = reset($tanModes);
}
$log('Auswahl: ' . $selectedTanMode->getName() . ' (ID ' . $selectedTanMode->getId() . ')');
$selectedTanMedium = null; $selectedTanMedium = null;
if ($selectedTanMode->needsTanMedium()) { if ($selectedTanMode->needsTanMedium()) {
@ -288,11 +305,14 @@ class FinTsFetchService
$period = max(1, (int)$tanMode->getPeriodicDecoupledCheckDelaySeconds()); $period = max(1, (int)$tanMode->getPeriodicDecoupledCheckDelaySeconds());
for ($attempt = 0; $attempt < $maxChecks; $attempt++) { for ($attempt = 0; $attempt < $maxChecks; $attempt++) {
try { try {
$this->fints->checkDecoupledSubmission($action); if ($this->fints->checkDecoupledSubmission($action)) {
return true; return true;
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
sleep($period); $log(' → Polling-Fehler: ' . $e->getMessage());
return false;
} }
sleep($period);
} }
$log(' → Zeitüberschreitung bei App-Bestätigung.'); $log(' → Zeitüberschreitung bei App-Bestätigung.');
return false; return false;

View file

@ -21,6 +21,7 @@ $connectionsJson = json_encode(array_map(fn($c) => [
'benutzerkennung' => $c['benutzerkennung'] ?? '', 'benutzerkennung' => $c['benutzerkennung'] ?? '',
'account_filter' => $c['account_filter'] ?? null, 'account_filter' => $c['account_filter'] ?? null,
'fetch_range' => $c['fetch_range'] ?? 'last_fetched', 'fetch_range' => $c['fetch_range'] ?? 'last_fetched',
'tan_mode_id' => isset($c['tan_mode_id']) && $c['tan_mode_id'] !== null ? (int)$c['tan_mode_id'] : null,
], $connections)); ], $connections));
$fetchRangeOptions = [ $fetchRangeOptions = [
@ -292,6 +293,11 @@ $fetchRangeOptions = [
<label for="edit-fints-url">FinTS-Server-URL <span class="text-muted text-sm">(optional)</span></label> <label for="edit-fints-url">FinTS-Server-URL <span class="text-muted text-sm">(optional)</span></label>
<input type="url" id="edit-fints-url" name="fints_url" placeholder="https://..."> <input type="url" id="edit-fints-url" name="fints_url" placeholder="https://...">
</div> </div>
<div class="form-group">
<label for="edit-tan-mode-id">TAN-Verfahren-ID <span class="text-muted text-sm">(optional)</span></label>
<input type="number" id="edit-tan-mode-id" name="tan_mode_id" min="1" placeholder="z.B. 913 für chipTAN QR">
<div class="text-muted text-sm">Leer = erstes verfügbares Verfahren</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="edit-fetch-range-fints">Abrufen ab</label> <label for="edit-fetch-range-fints">Abrufen ab</label>
<select id="edit-fetch-range-fints" name="fetch_range" disabled> <select id="edit-fetch-range-fints" name="fetch_range" disabled>
@ -463,6 +469,7 @@ $fetchRangeOptions = [
const editBenutzer = document.getElementById('edit-benutzerkennung'); const editBenutzer = document.getElementById('edit-benutzerkennung');
const editPin = document.getElementById('edit-pin'); const editPin = document.getElementById('edit-pin');
const editFintsUrl = document.getElementById('edit-fints-url'); const editFintsUrl = document.getElementById('edit-fints-url');
const editTanModeId = document.getElementById('edit-tan-mode-id');
const editAccountList = document.getElementById('edit-account-list'); const editAccountList = document.getElementById('edit-account-list');
const editResetAcc = document.getElementById('edit-reset-accounts'); const editResetAcc = document.getElementById('edit-reset-accounts');
const editFetchRangeApi = document.getElementById('edit-fetch-range-api'); const editFetchRangeApi = document.getElementById('edit-fetch-range-api');
@ -839,10 +846,11 @@ $fetchRangeOptions = [
editSandbox.checked = conn.sandbox === true; editSandbox.checked = conn.sandbox === true;
} }
if (isFints) { if (isFints) {
editBlz.value = conn.blz || ''; editBlz.value = conn.blz || '';
editBenutzer.value = conn.benutzerkennung || ''; editBenutzer.value = conn.benutzerkennung || '';
editPin.value = ''; editPin.value = '';
editFintsUrl.value = conn.fints_url || ''; editFintsUrl.value = conn.fints_url || '';
editTanModeId.value = conn.tan_mode_id != null ? conn.tan_mode_id : '';
editResetAcc.checked = false; editResetAcc.checked = false;
// Konto-Checkboxen aus account_filter aufbauen // Konto-Checkboxen aus account_filter aufbauen