Online-Banking: Einrichtung in zwei Schritten
Die Bank lässt sich erst nach ihren TAN-Verfahren fragen, wenn die Zugangsdaten gespeichert sind. Das Anlegen führt deshalb über „Weiter“ direkt auf die Verfahrensseite, und die holt die Liste selbst – ein Knopf davor wäre ein Schritt, dessen Ergebnis feststeht. Scheitert die Abfrage, bleibt das Ergebnis in der Sitzung, damit nicht jeder Seitenaufruf wieder in dieselbe Zeitüberschreitung läuft; die ist für die Abfrage kürzer gesetzt als in der Bibliothek. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4bd7ffed1b
commit
9111eeeb91
8 changed files with 207 additions and 94 deletions
15
Changes.md
15
Changes.md
|
|
@ -60,9 +60,18 @@ Damit ist der geplante Funktionsumfang beisammen: Von der Kontenübersicht bis z
|
|||
- Umsätze, die vor einer TAN-Unterbrechung geschrieben wurden, bleiben erhalten.
|
||||
|
||||
**Wahl des TAN-Verfahrens** (`Services\FinTsVerfahren`, Migration `009_tan_verfahren.sql`)
|
||||
- Eigene Seite am Zugang (`/banking/{id}/verfahren`): Der Knopf *Verfahren bei der Bank
|
||||
abfragen* holt die Liste über `getTanModes()` – das kommt ohne Anmeldung aus und verlangt
|
||||
selbst keine TAN. TAN-Medien werden nur für die Verfahren geholt, die eines brauchen.
|
||||
- **Einrichtung in zwei Schritten**: Zugangsdaten erfassen und mit *Weiter* speichern, dann
|
||||
das TAN-Verfahren. Vorher lässt sich die Bank nicht fragen – ohne gespeicherte
|
||||
Zugangsdaten gibt es keine Verbindung.
|
||||
- Der zweite Schritt holt die Liste beim Aufruf selbst (`getTanModes()`, ohne Anmeldung und
|
||||
ohne TAN); ein Knopf davor wäre ein Schritt, dessen Ergebnis feststeht. Ein erneuter
|
||||
Anlauf steht daneben. TAN-Medien werden nur für die Verfahren geholt, die eines brauchen.
|
||||
- Das Ergebnis liegt in der Sitzung, auch ein gescheitertes – sonst liefe jeder
|
||||
Seitenaufruf wieder in dieselbe Zeitüberschreitung. Die sind für die Abfrage kürzer
|
||||
gesetzt als in der Bibliothek (8/20 statt 15/30 Sekunden), weil sie an einem
|
||||
Seitenaufruf hängt.
|
||||
- Scheitert die Abfrage, erklärt die Seite, wo der Fehler meist steckt, und verlinkt zurück
|
||||
auf die Zugangsdaten; überspringen lässt sich der Schritt ebenfalls.
|
||||
- Abgefragt statt eingetippt: Die Kennungen sind bankspezifisch, 913 heißt nicht überall
|
||||
chipTAN QR. Der Name wird mitgespeichert (`tan_verfahren_name`), damit das Formular ihn
|
||||
ohne Bankverbindung zeigen kann.
|
||||
|
|
|
|||
|
|
@ -58,22 +58,39 @@ ausdrücklich aus. Sie wird bei jedem Request neu aus der Datenbank geholt und e
|
|||
fragt die Anwendung auf Knopfdruck nach (`checkDecoupledSubmission`) – steht die Freigabe
|
||||
noch aus, bleibt die Maske stehen.
|
||||
|
||||
### Das Verfahren wählen
|
||||
### Einrichtung in zwei Schritten
|
||||
|
||||
Fast jede Bank bietet mehrere Verfahren an. Welches gilt, steht am Zugang unter
|
||||
*TAN-Verfahren einstellen* (`/banking/{id}/verfahren`).
|
||||
Fast jede Bank bietet mehrere Verfahren an, und welche das sind, weiß nur sie. Fragen lässt
|
||||
sie sich aber erst, wenn Bankleitzahl, FinTS-Adresse, Benutzerkennung und PIN vorliegen –
|
||||
deshalb zerfällt das Anlegen einer Bankverbindung in zwei Schritte:
|
||||
|
||||
1. **Zugangsdaten** (`/banking/neu`) – *Weiter* speichert sie.
|
||||
2. **TAN-Verfahren** (`/banking/{id}/verfahren`) – die Seite fragt die Bank beim Aufruf
|
||||
selbst und zeigt die Liste zur Wahl.
|
||||
|
||||
Der zweite Schritt holt die Liste **ohne Knopfdruck**: Wer hier landet, will genau das, und
|
||||
ein Knopf, dessen Ergebnis feststeht, wäre ein Schritt zu viel. Ein erneuter Anlauf steht
|
||||
trotzdem bereit, wenn sich etwas geändert hat.
|
||||
|
||||
Später erreicht man dieselbe Seite über *TAN-Verfahren einstellen* am Zugang.
|
||||
|
||||
**Abgefragt, nicht eingetippt.** Die Kennungen sind bankspezifisch – 913 heißt nicht
|
||||
überall chipTAN QR –, und niemand soll sie in einer PDF der Bank suchen müssen. Der Knopf
|
||||
*Verfahren bei der Bank abfragen* holt die Liste über `getTanModes()`. Das kommt ohne
|
||||
Anmeldung aus (die Bibliothek braucht dafür nur die Bankparameterdaten) und verlangt
|
||||
deshalb selbst keine TAN.
|
||||
überall chipTAN QR –, und niemand soll sie in einer PDF der Bank suchen müssen.
|
||||
`getTanModes()` kommt ohne Anmeldung aus (die Bibliothek braucht dafür nur die
|
||||
Bankparameterdaten) und verlangt deshalb selbst keine TAN.
|
||||
|
||||
`getTanMedia()` wird nur für die Verfahren aufgerufen, die ein Medium brauchen: Jeder
|
||||
Aufruf ist ein eigener Dialog mit der Bank.
|
||||
|
||||
Die abgefragte Liste liegt in der Sitzung, nicht in der Datenbank – sie ist eine
|
||||
Momentaufnahme. Beim Übernehmen wird gegen sie geprüft: Eine Kennung, die die Bank nicht
|
||||
Die Zeitüberschreitungen sind kürzer gesetzt als in der Bibliothek (8 statt 15 Sekunden zum
|
||||
Verbinden, 20 statt 30 zur Antwort): Die Abfrage hängt an einem Seitenaufruf.
|
||||
|
||||
Das Ergebnis liegt in der Sitzung, **auch ein gescheitertes** – sonst liefe jeder
|
||||
Seitenaufruf erneut in dieselbe Zeitüberschreitung. Scheitert die Abfrage, erklärt die
|
||||
Seite, wo der Fehler meist steckt, und der Weg zurück zu den Zugangsdaten steht daneben.
|
||||
Die Wahl lässt sich überspringen; die Verbindung ist auch ohne sie benutzbar.
|
||||
|
||||
Beim Übernehmen wird gegen die abgefragte Liste geprüft: Eine Kennung, die die Bank nicht
|
||||
angeboten hat, wäre beim nächsten Abruf wirkungslos.
|
||||
|
||||
**Ohne Wahl** nimmt der erste Abruf das erste Verfahren, das die Bank meldet, und hält es
|
||||
|
|
@ -81,8 +98,7 @@ fest; sonst stünde im Formular nichts. Eine **ausdrückliche** Wahl wird dagege
|
|||
überschrieben: Bietet die Bank sie einmal nicht an, sagt das Protokoll das und die
|
||||
Einstellung bleibt stehen.
|
||||
|
||||
Das Verfahren steht bewusst nicht im Zugangsformular – dort ließe es sich nur als Zahl
|
||||
eintragen. Das Formular fasst es deshalb auch nicht an: Ohne diesen Vorbehalt setzte jedes
|
||||
Das Zugangsformular fasst Verfahren und Medium nicht an: Ohne diesen Vorbehalt setzte jedes
|
||||
Speichern die Wahl zurück.
|
||||
|
||||
Abruf und Überweisung benutzen dieselbe Wahl (`Services\FinTsVerfahren::anwenden()`) – eine
|
||||
|
|
|
|||
|
|
@ -307,7 +307,10 @@ form.inline { display: inline; margin: 0; }
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.phasenmarke {
|
||||
/* Kleine Marke: Zustand (Monatsabschluss), Dateiname (Import) oder der Schritt
|
||||
eines mehrteiligen Dialogs (Einrichtung einer Bankverbindung). */
|
||||
.phasenmarke,
|
||||
.schrittmarke {
|
||||
display: inline-block;
|
||||
background: var(--akzent-schwach);
|
||||
color: var(--akzent);
|
||||
|
|
|
|||
|
|
@ -70,9 +70,11 @@ class BankingController extends BaseController
|
|||
return;
|
||||
}
|
||||
|
||||
// Weiter zum zweiten Schritt: Erst jetzt – mit gespeicherten Zugangsdaten –
|
||||
// lässt sich die Bank nach ihren TAN-Verfahren fragen.
|
||||
View::redirect(
|
||||
'/banking/' . $id,
|
||||
'Bankverbindung angelegt. Beim ersten Abruf werden die Konten der Bank ermittelt.'
|
||||
'/banking/' . $id . '/verfahren?einrichtung=1',
|
||||
'Zugangsdaten gespeichert. Jetzt das TAN-Verfahren wählen.'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -127,9 +129,15 @@ class BankingController extends BaseController
|
|||
// ── TAN-Verfahren ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Die abgefragte Verfahrensliste. Sie liegt in der Sitzung, damit ein
|
||||
* erneutes Anzeigen der Seite – etwa nach einer Fehlermeldung – nicht jedes
|
||||
* Mal wieder die Bank behelligt.
|
||||
* Der zweite Schritt der Einrichtung: das TAN-Verfahren.
|
||||
*
|
||||
* Die Liste wird beim Aufruf **selbst geholt** – erst mit gespeicherten
|
||||
* Zugangsdaten lässt sich die Bank fragen, und genau dann steht der Nutzer
|
||||
* hier. Ein eigener Knopf davor wäre ein Schritt, dessen Ergebnis ohnehin
|
||||
* feststeht.
|
||||
*
|
||||
* Das Ergebnis bleibt in der Sitzung, auch ein gescheitertes: Sonst liefe
|
||||
* jeder Seitenaufruf erneut in dieselbe Zeitüberschreitung.
|
||||
*
|
||||
* @param array<string,string> $params
|
||||
*/
|
||||
|
|
@ -144,33 +152,32 @@ class BankingController extends BaseController
|
|||
return;
|
||||
}
|
||||
|
||||
$abfrage = $this->gemerkteAbfrage($id) ?? $this->verfahrenHolen($id);
|
||||
|
||||
$this->render('banking/verfahren.php', [
|
||||
'titel' => 'TAN-Verfahren – ' . $zugang['name'],
|
||||
'zugang' => $zugang,
|
||||
'medium' => Crypto::entschluessle((string)($zugang['tan_medium'] ?? '')),
|
||||
'verfahren' => $this->gemerkteVerfahren($id),
|
||||
'verfahren' => $abfrage['liste'],
|
||||
'fehler' => $abfrage['status'] === 'fehler' ? $abfrage['meldung'] : '',
|
||||
'einrichtung' => $this->query('einrichtung') === '1',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @param array<string,string> $params */
|
||||
/**
|
||||
* Ein erneuter Anlauf, wenn der erste scheiterte oder sich etwas geändert hat.
|
||||
*
|
||||
* @param array<string,string> $params
|
||||
*/
|
||||
public function verfahrenAbfragen(array $params): void
|
||||
{
|
||||
$this->verlangeAnmeldung();
|
||||
|
||||
$id = (int)$params['id'];
|
||||
$ergebnis = FinTsVerfahren::abfragen($id);
|
||||
$abfrage = $this->verfahrenHolen($id);
|
||||
$ziel = '/banking/' . $id . '/verfahren' . ($this->post('einrichtung') === '1' ? '?einrichtung=1' : '');
|
||||
|
||||
Auth::startSession();
|
||||
$_SESSION[self::SITZUNG_VERFAHREN] = [
|
||||
'zugang_id' => $id,
|
||||
'liste' => $ergebnis['verfahren'],
|
||||
];
|
||||
|
||||
View::redirect(
|
||||
'/banking/' . $id . '/verfahren',
|
||||
$ergebnis['meldung'],
|
||||
$ergebnis['status'] === 'fehler' ? 'fehler' : 'erfolg'
|
||||
);
|
||||
View::redirect($ziel, $abfrage['meldung'], $abfrage['status'] === 'fehler' ? 'fehler' : 'erfolg');
|
||||
}
|
||||
|
||||
/** @param array<string,string> $params */
|
||||
|
|
@ -180,6 +187,7 @@ class BankingController extends BaseController
|
|||
|
||||
$id = (int)$params['id'];
|
||||
$verfahren = (int)$this->post('verfahren_id');
|
||||
$abfrage = $this->gemerkteAbfrage($id);
|
||||
|
||||
try {
|
||||
FinTsVerfahren::uebernehmen(
|
||||
|
|
@ -188,31 +196,61 @@ class BankingController extends BaseController
|
|||
// Das Medium des angekreuzten Verfahrens, nicht das irgendeiner
|
||||
// anderen Zeile.
|
||||
$this->post('tan_medium_' . $verfahren),
|
||||
$this->gemerkteVerfahren($id)
|
||||
$abfrage === null ? [] : $abfrage['liste']
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
View::redirect('/banking/' . $id . '/verfahren', $e->getMessage(), 'fehler');
|
||||
return;
|
||||
}
|
||||
|
||||
View::redirect('/banking/' . $id, 'TAN-Verfahren übernommen.');
|
||||
View::redirect(
|
||||
'/banking/' . $id,
|
||||
$this->post('einrichtung') === '1'
|
||||
? 'Bankverbindung eingerichtet. Der erste Abruf ermittelt jetzt die Konten.'
|
||||
: 'TAN-Verfahren übernommen.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragt die Bank und merkt sich das Ergebnis für diese Sitzung.
|
||||
*
|
||||
* @return array{status: string, meldung: string, liste: list<array<string,mixed>>}
|
||||
*/
|
||||
private function verfahrenHolen(int $zugangId): array
|
||||
{
|
||||
$ergebnis = FinTsVerfahren::abfragen($zugangId);
|
||||
|
||||
$abfrage = [
|
||||
'status' => $ergebnis['status'],
|
||||
'meldung' => $ergebnis['meldung'],
|
||||
'liste' => $ergebnis['verfahren'],
|
||||
];
|
||||
|
||||
Auth::startSession();
|
||||
$_SESSION[self::SITZUNG_VERFAHREN] = ['zugang_id' => $zugangId] + $abfrage;
|
||||
|
||||
return $abfrage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Die zuletzt abgefragte Liste – aber nur die zu diesem Zugang.
|
||||
*
|
||||
* @return list<array<string,mixed>>
|
||||
* @return array{status: string, meldung: string, liste: list<array<string,mixed>>}|null
|
||||
*/
|
||||
private function gemerkteVerfahren(int $zugangId): array
|
||||
private function gemerkteAbfrage(int $zugangId): ?array
|
||||
{
|
||||
Auth::startSession();
|
||||
$gemerkt = $_SESSION[self::SITZUNG_VERFAHREN] ?? null;
|
||||
|
||||
if (!is_array($gemerkt) || (int)($gemerkt['zugang_id'] ?? 0) !== $zugangId) {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_array($gemerkt['liste'] ?? null) ? $gemerkt['liste'] : [];
|
||||
return [
|
||||
'status' => (string)($gemerkt['status'] ?? 'fehler'),
|
||||
'meldung' => (string)($gemerkt['meldung'] ?? ''),
|
||||
'liste' => is_array($gemerkt['liste'] ?? null) ? $gemerkt['liste'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
// ── Abruf ────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -278,6 +278,12 @@ class FinTsVerfahren
|
|||
$optionen->productName = Einstellung::get('banking_produkt_id') ?: self::PRODUKT;
|
||||
$optionen->productVersion = self::PRODUKT_VERSION;
|
||||
|
||||
// Kürzer als die Vorgabe der Bibliothek (15/30): Die Abfrage hängt an
|
||||
// einem Seitenaufruf, und eine Bank, die nicht antwortet, soll die Seite
|
||||
// nicht minutenlang aufhalten.
|
||||
$optionen->timeoutConnect = 8;
|
||||
$optionen->timeoutResponse = 20;
|
||||
|
||||
$fints = FinTs::new(
|
||||
$optionen,
|
||||
Credentials::create((string)$zugang['benutzerkennung'], (string)$zugang['pin'])
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ $wert = static fn(string $feld, string $standard = ''): string => (string)($zu
|
|||
include __DIR__ . '/../layout/kopf.php';
|
||||
?>
|
||||
|
||||
<?php if ($neu): ?>
|
||||
<p class="schrittmarke">Schritt 1 von 2 · Zugangsdaten</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="seitenaktionen">
|
||||
<a href="/banking" class="knopf">Zurück zu den Bankverbindungen</a>
|
||||
<?php if (!$neu): ?>
|
||||
|
|
@ -95,9 +99,7 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
Die Wahl steht auf einer eigenen Seite, weil dafür die Bank gefragt werden muss:
|
||||
<a href="/banking/<?= (int)$zugang['id'] ?>/verfahren">TAN-Verfahren einstellen</a>.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$neu): ?>
|
||||
<h3>Konten bei dieser Bank</h3>
|
||||
<?php if ($konten === []): ?>
|
||||
<p class="platzhalter">
|
||||
|
|
@ -153,8 +155,15 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($neu): ?>
|
||||
<p class="feldhinweis">
|
||||
Nach dem Speichern fragt der Finanzmanager die Bank, welche TAN-Verfahren sie für
|
||||
diese Benutzerkennung anbietet – ohne gespeicherte Zugangsdaten geht das nicht.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="knopfleiste">
|
||||
<button type="submit" class="knopf primaer"><?= $neu ? 'Anlegen' : 'Speichern' ?></button>
|
||||
<button type="submit" class="knopf primaer"><?= $neu ? 'Weiter' : 'Speichern' ?></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,74 +3,81 @@
|
|||
use Finanzmanager\View;
|
||||
|
||||
/**
|
||||
* Die Wahl des TAN-Verfahrens für einen Bankzugang.
|
||||
* Schritt 2 der Einrichtung: das TAN-Verfahren.
|
||||
*
|
||||
* Die Liste hat der Controller schon geholt – hier steht nur noch die Wahl.
|
||||
*
|
||||
* @var array<string,mixed> $zugang
|
||||
* @var string $medium Das gespeicherte TAN-Medium im Klartext
|
||||
* @var list<array{id:int, name:string, entkoppelt:bool, medium_noetig:bool,
|
||||
* medien:list<string>, hinweis:string}> $verfahren
|
||||
* @var string $fehler Meldung, wenn die Abfrage scheiterte
|
||||
* @var bool $einrichtung Teil des Einrichtungsablaufs?
|
||||
* @var string $csrfToken
|
||||
*/
|
||||
|
||||
$gewaehlt = $zugang['tan_verfahren_id'] === null ? null : (int)$zugang['tan_verfahren_id'];
|
||||
$zugangId = (int)$zugang['id'];
|
||||
|
||||
include __DIR__ . '/../layout/kopf.php';
|
||||
?>
|
||||
|
||||
<?php if ($einrichtung): ?>
|
||||
<p class="schrittmarke">Schritt 2 von 2 · TAN-Verfahren</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="seitenaktionen">
|
||||
<a href="/banking/<?= (int)$zugang['id'] ?>" class="knopf">Zurück zur Bankverbindung</a>
|
||||
<form method="post" action="/banking/<?= (int)$zugang['id'] ?>/verfahren/abfragen" class="inline">
|
||||
<a href="/banking/<?= $zugangId ?>" class="knopf">
|
||||
<?= $einrichtung ? 'Später einstellen' : 'Zurück zur Bankverbindung' ?>
|
||||
</a>
|
||||
<form method="post" action="/banking/<?= $zugangId ?>/verfahren/abfragen" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||
<button type="submit" class="knopf primaer">
|
||||
<?= $verfahren === [] ? 'Verfahren bei der Bank abfragen' : 'Erneut abfragen' ?>
|
||||
</button>
|
||||
<input type="hidden" name="einrichtung" value="<?= $einrichtung ? '1' : '0' ?>">
|
||||
<button type="submit" class="knopf">Erneut bei der Bank abfragen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if ($fehler !== ''): ?>
|
||||
<p class="meldung meldung-fehler" role="status">
|
||||
<?= View::h($fehler) ?>
|
||||
</p>
|
||||
<p class="feldhinweis">
|
||||
Welche Verfahren zur Verfügung stehen, weiß nur die Bank – die Kennungen sind
|
||||
von Haus zu Haus verschieden. Die Abfrage kommt ohne Anmeldung aus und verlangt
|
||||
deshalb selbst keine TAN.
|
||||
</p>
|
||||
|
||||
<h3>Zurzeit eingestellt</h3>
|
||||
<?php if ($gewaehlt === null): ?>
|
||||
<p class="platzhalter">
|
||||
Noch nichts gewählt. Beim nächsten Abruf nimmt der Finanzmanager das erste
|
||||
Verfahren, das die Bank meldet, und merkt es sich – das muss nicht das sein,
|
||||
mit dem du sonst arbeitest.
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p>
|
||||
<strong>
|
||||
<?= View::h(($zugang['tan_verfahren_name'] ?? '') !== ''
|
||||
? $zugang['tan_verfahren_name'] : 'Verfahren ' . $gewaehlt) ?>
|
||||
</strong>
|
||||
<span class="nebentext">Kennung <?= $gewaehlt ?></span>
|
||||
Meist stimmt eine Angabe im ersten Schritt nicht – Bankleitzahl, FinTS-Adresse,
|
||||
Benutzerkennung oder PIN.
|
||||
<a href="/banking/<?= $zugangId ?>">Zugangsdaten prüfen</a>.
|
||||
Die Wahl lässt sich auch später nachholen; ohne sie nimmt der erste Abruf das erste
|
||||
Verfahren, das die Bank meldet.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($verfahren === []): ?>
|
||||
<p class="platzhalter">
|
||||
Noch keine Liste abgefragt. Der Knopf oben holt sie – dafür müssen
|
||||
Bankleitzahl, FinTS-Adresse, Benutzerkennung und PIN gespeichert sein.
|
||||
</p>
|
||||
<?php if ($fehler === ''): ?>
|
||||
<p class="platzhalter">Die Bank hat kein Verfahren genannt.</p>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<h3>Von der Bank angeboten</h3>
|
||||
<form method="post" action="/banking/<?= (int)$zugang['id'] ?>/verfahren" class="formular breit">
|
||||
<p class="feldhinweis">
|
||||
Welche Verfahren zur Verfügung stehen, weiß nur die Bank – die Kennungen sind von
|
||||
Haus zu Haus verschieden. Zu wählen ist das, mit dem du sonst arbeitest.
|
||||
</p>
|
||||
|
||||
<form method="post" action="/banking/<?= $zugangId ?>/verfahren" class="formular breit">
|
||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||
<input type="hidden" name="einrichtung" value="<?= $einrichtung ? '1' : '0' ?>">
|
||||
|
||||
<table class="liste">
|
||||
<thead>
|
||||
<tr><th>Wahl</th><th>Verfahren</th><th>Kennung</th><th>TAN-Medium</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($verfahren as $eintrag): ?>
|
||||
<?php foreach ($verfahren as $nummer => $eintrag): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php /* Ohne Vorwahl bliebe das Formular leer abschickbar; die
|
||||
erste Zeile ist die naheliegende Voreinstellung. */ ?>
|
||||
<input type="radio" name="verfahren_id" id="verfahren-<?= (int)$eintrag['id'] ?>"
|
||||
value="<?= (int)$eintrag['id'] ?>"
|
||||
<?= $gewaehlt === (int)$eintrag['id'] ? 'checked' : '' ?>>
|
||||
<?= $gewaehlt === (int)$eintrag['id'] || ($gewaehlt === null && $nummer === 0)
|
||||
? 'checked' : '' ?>>
|
||||
</td>
|
||||
<td>
|
||||
<label for="verfahren-<?= (int)$eintrag['id'] ?>">
|
||||
|
|
@ -107,9 +114,30 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
</table>
|
||||
|
||||
<div class="knopfleiste">
|
||||
<button type="submit" class="knopf primaer">Verfahren übernehmen</button>
|
||||
<button type="submit" class="knopf primaer">
|
||||
<?= $einrichtung ? 'Einrichtung abschließen' : 'Verfahren übernehmen' ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$einrichtung): ?>
|
||||
<h3>Zurzeit eingestellt</h3>
|
||||
<?php if ($gewaehlt === null): ?>
|
||||
<p class="platzhalter">
|
||||
Noch nichts gewählt. Beim nächsten Abruf nimmt der Finanzmanager das erste
|
||||
Verfahren, das die Bank meldet, und merkt es sich.
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p>
|
||||
<strong>
|
||||
<?= View::h(($zugang['tan_verfahren_name'] ?? '') !== ''
|
||||
? $zugang['tan_verfahren_name'] : 'Verfahren ' . $gewaehlt) ?>
|
||||
</strong>
|
||||
<span class="nebentext">Kennung <?= $gewaehlt ?><?= $medium !== ''
|
||||
? ', Medium ' . View::h($medium) : '' ?></span>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include __DIR__ . '/../layout/fuss.php'; ?>
|
||||
|
|
|
|||
|
|
@ -46,11 +46,11 @@ test.describe('Module der Phasen 4 bis 7', () => {
|
|||
['/verwaltung', 'Verwaltung'],
|
||||
]
|
||||
|
||||
// Jeder Bereich hat sein Modul: Die Überschrift gehört zum Modul, nicht
|
||||
// zur früheren Platzhalterseite.
|
||||
for (const [pfad, titel] of punkte) {
|
||||
await page.goto(pfad)
|
||||
await expect(page.locator('h1')).toContainText(titel)
|
||||
// Kein Platzhalter mehr: Alle Bereiche haben ihr Modul.
|
||||
await expect(page.locator('.phasenmarke')).toHaveCount(0)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -94,24 +94,28 @@ test.describe('Module der Phasen 4 bis 7', () => {
|
|||
await page.fill('#fints_url', 'https://banking.example.org/fints')
|
||||
await page.fill('#benutzerkennung', 'e2e-kunde')
|
||||
await page.fill('#pin', 'geheim')
|
||||
await page.click('button:has-text("Anlegen")')
|
||||
|
||||
await expect(page.locator('.meldung')).toContainText('angelegt')
|
||||
// Schritt 1 speichert nur die Zugangsdaten.
|
||||
await expect(page.locator('.schrittmarke')).toContainText('Schritt 1 von 2')
|
||||
await page.click('button:has-text("Weiter")')
|
||||
|
||||
// Schritt 2 fragt die Bank selbst nach ihren TAN-Verfahren. Die Adresse
|
||||
// im Test führt nirgendwohin – geprüft wird deshalb der Fehlerweg: Die
|
||||
// Seite erklärt, was zu tun ist, statt stehen zu bleiben.
|
||||
await expect(page.locator('.schrittmarke')).toContainText('Schritt 2 von 2')
|
||||
await expect(page.locator('h1')).toContainText('E2E-Testbank')
|
||||
await expect(page.locator('.meldung-fehler')).toBeVisible()
|
||||
await expect(page.locator('a:has-text("Zugangsdaten prüfen")')).toHaveCount(1)
|
||||
|
||||
await page.click('a:has-text("Später einstellen")')
|
||||
await expect(page.locator('h1')).toContainText('E2E-Testbank')
|
||||
|
||||
// Die PIN wird nie wieder angezeigt.
|
||||
await expect(page.locator('#pin')).toHaveValue('')
|
||||
await expect(page.locator('#pin')).toHaveAttribute('placeholder', /unverändert/)
|
||||
|
||||
// Das TAN-Verfahren steht auf einer eigenen Seite. Ohne Abfrage bei der
|
||||
// Bank ist dort nichts gewählt – abgefragt wird hier nicht, das bräuchte
|
||||
// eine echte Bankverbindung.
|
||||
await page.click('a:has-text("TAN-Verfahren einstellen")')
|
||||
await expect(page.locator('h1')).toContainText('TAN-Verfahren')
|
||||
await expect(page.locator('.platzhalter').first()).toContainText('Noch nichts gewählt')
|
||||
await expect(page.locator('button:has-text("Verfahren bei der Bank abfragen")')).toHaveCount(1)
|
||||
|
||||
await page.click('a:has-text("Zurück zur Bankverbindung")')
|
||||
// Ohne Wahl bleibt der Hinweis stehen, und die Seite ist wieder erreichbar.
|
||||
await expect(page.locator('a:has-text("TAN-Verfahren einstellen")')).toHaveCount(1)
|
||||
|
||||
page.once('dialog', dialog => dialog.accept())
|
||||
await page.click('button:has-text("Bankverbindung löschen")')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue