From d30c0c60a9b3257f75721534288c257e59732686 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Tue, 25 Mar 2025 13:03:54 +0100 Subject: [PATCH 01/19] version bump --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ade9ea0d..03c9e595 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", "homepage": "https://github.com/nemiah/phpFinTS", - "version": "3.5.0", + "version": "3.6.0", "license": "MIT", "autoload": { "psr-0": { From 6a43c769bd15f80e17dba5b0cbc380a7ffec87f1 Mon Sep 17 00:00:00 2001 From: Ansas Meyer Date: Mon, 16 Jun 2025 17:30:37 +0200 Subject: [PATCH 02/19] Update HICMEv1.php Commit 5e147cc bugfix --- lib/Fhp/Segment/CME/HICMEv1.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Segment/CME/HICMEv1.php b/lib/Fhp/Segment/CME/HICMEv1.php index 9b75693a..4fb50613 100644 --- a/lib/Fhp/Segment/CME/HICMEv1.php +++ b/lib/Fhp/Segment/CME/HICMEv1.php @@ -2,7 +2,7 @@ namespace Fhp\Segment\CME; -use Fhp\Segment\BaseGeschaeftsvorfallparameter; +use Fhp\Segment\BaseSegment; /** * Segment: SEPA Einzelüberweisung Parameter (Version 1) From dbcd9f7b7f4ca1701a3c193c5d41234d0409eaec Mon Sep 17 00:00:00 2001 From: Ansas Meyer Date: Mon, 16 Jun 2025 17:31:18 +0200 Subject: [PATCH 03/19] Update HICSEv1.php Commit 5e147cc bugfix --- lib/Fhp/Segment/CSE/HICSEv1.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Segment/CSE/HICSEv1.php b/lib/Fhp/Segment/CSE/HICSEv1.php index 1b17e5dc..4bedf9fe 100644 --- a/lib/Fhp/Segment/CSE/HICSEv1.php +++ b/lib/Fhp/Segment/CSE/HICSEv1.php @@ -2,7 +2,7 @@ namespace Fhp\Segment\CSE; -use Fhp\Segment\BaseGeschaeftsvorfallparameter; +use Fhp\Segment\BaseSegment; /** * Segment: SEPA Einzelüberweisung Parameter (Version 1) From 184adc937b18a983fd851b6e610f62562c5abc54 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sat, 9 Aug 2025 15:28:03 +0200 Subject: [PATCH 04/19] Clarify post condition of submitTan() Re second question on #485. --- lib/Fhp/FinTs.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 426ff10c..70006244 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -337,6 +337,9 @@ public function execute(BaseAction $action) * `false`, this function sends the given $tan to the server in order to complete the action. This can be done * asynchronously, i.e. not in the same PHP process as the original {@link execute()} call. * + * After this function returns, the `$action` is completed. That is, its result is available through its getters + * just as if it had been completed by the original call to {@link execute()} right away. + * * @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_2020-07-10_final_version.pdf * Section B.4.2.1.1 * From cb27985d442000abdb745f3f58cee87273c36a5f Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sat, 16 Aug 2025 12:45:33 +0200 Subject: [PATCH 05/19] Improve documentation around FinTs::persist() --- lib/Fhp/FinTs.php | 120 +++++++++++++++++++++++++++++++--------------- 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 70006244..a124c27a 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -32,7 +32,7 @@ /** * This is the main class of this library. Please see the Samples directory for how to use it. - * This class is not thread-safe, do not call its funtions concurrently. + * This class is not thread-safe. Do not call its funtions concurrently. */ class FinTs { @@ -50,7 +50,7 @@ class FinTs /** @var string|null This is a {@link TanMedium::getName()}, but we don't have the {@link TanMedium} instance. */ private $selectedTanMedium; - // State that persists across physical connections, dialogs and even PHP executions. + // State that persists across physical connections, dialogs and even PHP processes. /** @var BPD|null */ private $bpd; /** @var int[]|null The IDs of the {@link TanMode}s from the BPD which the user is allowed to use. */ @@ -74,8 +74,8 @@ class FinTs * @param Credentials $credentials Authentication information for the user. Note: This library does not support * anonymous connections, so the credentials are mandatory. * @param string|null $persistedInstance The return value of {@link persist()} of a previous FinTs instance, - * usually from an earlier PHP execution. Passing this in here saves 1-2 dialogs that are normally made with the - * bank to obtain the BPD and Kundensystem-ID. + * usually from an earlier PHP process. NOTE: Each persisted instance may be used only once and should be + * considered invalid afterwards. To continue the session, call {@link persist()} again. */ public static function new(FinTsOptions $options, Credentials $credentials, ?string $persistedInstance = null): FinTs { @@ -89,7 +89,7 @@ public static function new(FinTsOptions $options, Credentials $credentials, ?str } /** - * This function allows to fetch the BPD without knowing the user's credentials yet, by using an anonymous dialog. + * This function allows fetching the BPD without knowing the user's credentials yet, by using an anonymous dialog. * Note: If this fails with an error saying that your bank does not support the anonymous dialog, you probably need * to use {@link NoPsd2TanMode} for regular login. * @param FinTsOptions $options Configuration options for the connection to the bank. @@ -118,9 +118,8 @@ protected function __construct(FinTsOptions $options, ?Credentials $credentials) } /** - * Destructing the object only disconnects. Please use {@link close()} if you want to properly "log out", i.e. end - * the FinTs dialog. On the other hand, do *not* close in case you have serialized the FinTs instance and intend - * to resume it later due to a TAN request. + * Destructing the object only disconnects. Please use {@link close()} if you want to properly "log out", i.e., end + * the FinTs dialog. */ public function __destruct() { @@ -128,24 +127,38 @@ public function __destruct() } /** - * Returns a serialized form of parts of this object. This is different from PHP's `\Serializable` in that it only - * serializes parts and cannot simply be restored with `unserialize()` because the `FinTsOptions` and the - * `Credentials` need to be passed to FinTs::new() in addition to the string returned here. + * Returns a serialized form this object. This is different from PHP's {@link \Serializable} in that it only + * serializes parts and cannot simply be restored with {@link unserialize()}, because the {@link FinTsOptions} and + * the {@link Credentials} need to be passed to {@link FinTs::new()}, in addition to the string returned here. * - * Alternatively you can use {@link loadPersistedInstance) to separate constructing the instance and resuming it. + * Alternatively, you can use {@link loadPersistedInstance()} to separate constructing the instance and resuming it. * - * NOTE: Unless you're persisting this object to complete a TAN request later on, you probably want to log the user - * out first by calling {@link close()}. + * There are broadly two reasons to persist the instance: + * 1. During login or certain other actions, you may encounter a TAN/2FA request ({@link BaseAction::needsTan()} + * returns true). In that case, you MUST call {@link submitTan()} or {@link checkDecoupledSubmission()} later, + * without losing the dialog state in between. Depending on your application's circumstances, one option might be + * to simply keep the {@link FinTs} instance itself alive in memory (e.g., in a CLI application, you can block + * until the user provides the TAN). In most server-based scenarios, however, the PHP process will shut down and + * a new PHP process will be started later, when the client calls again to provide the TAN. In this case, you + * need to persist the {@link FinTs} instance and restore it later in order for the action to succeed. + * 2. Even when there is no outstanding action and after logging out with {@link close()}, it's beneficial to + * persist the instance (with $minimal=false). By reusing the cached BPD, UPD and TAN mode information upon the + * next {@link login()}, a few roundtrips to the FinTS server can be avoided. * - * @param bool $minimal If true, the return value only contains only those values that are necessary to complete an - * outstanding TAN request, but not the relatively large BPD/UPD, which can always be retrieved again later with - * a few extra requests to the server. + * IMPORTANT: Each serialized instance (each value returned from {@link persist()}) can only be used once. After + * passing it to {@link FinTs::new()} or {@link loadPersistedInstance()}, you must consider it invalidated. To keep + * the same instance/session alive, you must call {@link persist()} again. + * + * @param bool $minimal If true, the return value only contains the values necessary to complete an outstanding TAN + * request, but not the relatively large BPD/UPD, which can always be retrieved again later with a few extra + * requests to the server. So the persisting doesn't work for use case (2.) from above, but in turn, it saves + * storage space. * @return string A serialized form of those parts of the FinTs instance that can reasonably be persisted (BPD, UPD, * Kundensystem-ID, etc.). Note that this usually contains some user data (user's name, account names and - * sometimes a dialog ID that is equivalent to session cookie), so the returned string needs to be treated + * sometimes a dialog ID that is equivalent to session cookie). So the returned string needs to be treated * carefully (not written to log files, only to a database or other storage system that would normally be used * for user data). The returned string never contains highly sensitive information (not the user's password or - * PIN), so it probably does not need to be encrypted. + * PIN), so it probably does not need to be encrypted. Treat it like a session cookie of the same bank. */ public function persist(bool $minimal = false): string { @@ -175,11 +188,16 @@ public function __unserialize(array $data): void } /** - * Use this to continue a previous FinTs Instance, for example after a TAN was needed and PHP execution was ended to - * obtain it from the user. + * Loads data from a previous {@link FinTs} instance, to reuse cached BPD/UPD information and/or to continue using + * an ongoing session. The latter is necessary to complete a TAN request when the user provides the TAN in a fresh + * PHP process. + * + * Unless it's not available to you at that time already, you can just pass the persisted instance into + * {@link FinTs::new()} instead of calling this function. * * @param string $persistedInstance The return value of {@link persist()} of a previous FinTs instance, usually - * from an earlier PHP execution. + * from an earlier PHP process. NOTE: Each persisted instance may be used only once and should be considered + * invalid afterwards. To continue the session, call {@link persist()} again. * * @throws \InvalidArgumentException */ @@ -245,8 +263,10 @@ public function setTimeouts(int $connectTimeout, int $responseTimeout) /** * Executes a strongly authenticated login action and returns it. With some banks, this requires a TAN. * @return DialogInitialization A {@link BaseAction} for the outcome of the login. You should check whether a TAN is - * needed using {@link BaseAction::needsTan()} and, if so, finish the login by passing the {@link BaseAction} - * returned here to {@link submitTan()} or {@link checkDecoupledSubmission()}. + * needed using {@link BaseAction::needsTan()} and, if so, let the user complete the TAN request from + * {@link BaseAction::getTanRequest()} and then finish the login by passing the {@link BaseAction} + * returned here to {@link submitTan()} or {@link checkDecoupledSubmission()}. See {@link execute()} for + * details. * @throws CurlException When the connection fails in a layer below the FinTS protocol. * @throws UnexpectedResponseException When the server responds with a valid but unexpected message. * @throws ServerException When the server responds with a (FinTS-encoded) error message, which includes most things @@ -265,10 +285,18 @@ public function login(): DialogInitialization /** * Executes an action. Be sure to {@link login()} first. See the `\Fhp\Action` package for actions that can be - * executed with this function. Note that, after this function returns, the result of the action is stored inside - * the action itself, so you need to check {@link BaseAction::needsTan()} to see if it needs a TAN before being - * completed and use its getters in order to obtain the result. In case the action fails, the corresponding - * exception will be thrown from this function. + * executed with this function. Note that, after this function returns, the action can be in two possible states: + * 1. If {@link BaseAction::needsTan()} returns true, the action isn't completed yet because needs a TAN or other + * kind of two-factor authentication (2FA). In this case, use {@link BaseAction::getTanRequest()} to get more + * information about the TAN/2FA that is needed. Your application then needs to interact with the user to obtain + * the TAN (which should be passed into {@link submitTan()}) or to have them complete the 2FA check (which can + * be verified with {@link checkDecoupledSubmission()}). Both of those functions require passing the same + * {@link BaseAction} argument as an argument, and once they succeed, the action will be in the same completed + * state as if it had been completed right away. + * 2. If {@link BaseAction::needsTan()} returns false, the action was completed right away. Use the respective + * getters on the action instance to retrieve the result. In case the action fails, the corresponding exception + * will be thrown from this function. + * * @param BaseAction $action The action to be executed. Its {@link BaseAction::isDone()} status will be updated when * this function returns successfully. * @throws CurlException When the connection fails in a layer below the FinTS protocol. @@ -334,11 +362,12 @@ public function execute(BaseAction $action) /** * For an action where {@link BaseAction::needsTan()} returns `true` and {@link TanMode::isDecoupled()} returns - * `false`, this function sends the given $tan to the server in order to complete the action. This can be done - * asynchronously, i.e. not in the same PHP process as the original {@link execute()} call. + * `false`, this function sends the given $tan to the server to complete the action. By using {@link persist()}, + * this can be done asynchronously, i.e., not in the same PHP process as the original {@link execute()} call. * * After this function returns, the `$action` is completed. That is, its result is available through its getters - * just as if it had been completed by the original call to {@link execute()} right away. + * just as if it had been completed by the original call to {@link execute()} right away. In case the action fails, + * the corresponding exception will be thrown from this function. * * @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_2020-07-10_final_version.pdf * Section B.4.2.1.1 @@ -403,10 +432,18 @@ public function submitTan(BaseAction $action, string $tan) /** * For an action where {@link BaseAction::needsTan()} returns `true` and {@link TanMode::isDecoupled()} returns * `true`, this function checks with the server whether the second factor authentication has been completed yet on - * the secondary device of the user. If so, this completes the given action and returns `true`, otherwise it - * returns `false` and the action remains in its previous, uncompleted state. - * This function can be called asynchronously, i.e. not in the same PHP process as the original {@link execute()} - * call, and also repeatedly subject to the delays specified in the {@link TanMode}. + * the secondary device of the user. + * - If so, this completes the given action and returns `true`. + * - In case the action fails, the corresponding exception will be thrown from this function. + * - If the authentication has not been completed yet, this returns `false` and the action remains in its + * previous, uncompleted state. + * + * By using {@link persist()}, this function can be called asynchronously, i.e., not in the same PHP process as the + * original {@link execute()} call. + * + * This function can be called repeatedly, subject to the delays specified in the {@link TanMode}. + * IMPORTANT: Remember to re-{@link persist()} the {@link FinTs} instance after each + * {@link checkDecoupledSubmission()} call. * * @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_2020-07-10_final_version.pdf * Section B.4.2.2 @@ -494,7 +531,11 @@ public function checkDecoupledSubmission(BaseAction $action): bool } /** - * Closes open dialog/connection if any. This instance remains usable. + * Closes the session/dialog/connection, if open. This is equivalent to logging out. You should call this function + * when you're done with all the actions, but NOT when you're persisting the instance to fulfill the TAN request of + * an outstanding action. + * This FinTs object remains usable even after closing the session. You can still {@link persist()} it to benefit + * from cached BPD/UPD upon the next {@link login()}, for instance. * @throws ServerException When closing the dialog fails. */ public function close() @@ -506,9 +547,10 @@ public function close() } /** - * Assumes that the dialog (if any is open) is gone. This can be called by the application using this library when - * it just restored this FinTs instance from the persisted format after a long time, so that the dialog/session has - * most likely been closed at the server side already. + * Assumes that the session/dialog (if any is open) is gone, but keeps any cached BPD/UPD for reuse (to allow for + * faster re-login). + * This can be called by the application using this library when it just restored this FinTs instance from the + * persisted format after a long time, during which the session/dialog has most likely expired on the server side. */ public function forgetDialog() { From 35f2d2a543670a1110dc99329a138499ee1a5471 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sat, 16 Aug 2025 13:09:36 +0200 Subject: [PATCH 06/19] Demonstrate combined use of checkDecoupledSubmission and persist in code samples --- Samples/browser.php | 23 +++++++++++++++-- Samples/login.php | 62 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/Samples/browser.php b/Samples/browser.php index 3a16b5ce..b6cc2db4 100644 --- a/Samples/browser.php +++ b/Samples/browser.php @@ -58,18 +58,37 @@ function handleRequest(\stdClass $request, \Fhp\FinTs $fints) return ['result' => 'success']; case 'submitTan': $fints->submitTan(unserialize($persistedAction), $request->tan); + $persistedAction = null; return ['result' => 'success']; case 'checkDecoupledSubmission': if ($fints->checkDecoupledSubmission(unserialize($persistedAction))) { + $persistedAction = null; return ['result' => 'success']; } else { + // IMPORTANT: If you pull this example code apart in your real application code, remember that after + // calling checkDecoupledSubmission(), you need to call $fints->persist() again, just like this + // example code will do after we return from handleRequest() here. return ['result' => 'ongoing']; } case 'getBalances': $getAccounts = \Fhp\Action\GetSEPAAccounts::create(); - $fints->execute($getAccounts); // We assume that needsTan() is always false here. + $fints->execute($getAccounts); + if ($getAccounts->needsTan()) { + throw new \Fhp\UnsupportedException( + "This simple example code does not support strong authentication on GetSEPAAccounts calls. " . + "But in your real application, you can do so analogously to how login() is handled above." + ); + } + $getBalances = \Fhp\Action\GetBalance::create($getAccounts->getAccounts()[0], true); - $fints->execute($getBalances); // We assume that needsTan() is always false here. + $fints->execute($getBalances); + if ($getAccounts->needsTan()) { + throw new \Fhp\UnsupportedException( + "This simple example code does not support strong authentication on GetBalance calls. " . + "But in your real application, you can do so analogously to how login() is handled above." + ); + } + $balances = []; foreach ($getBalances->getBalances() as $balance) { $sdo = $balance->getGebuchterSaldo(); diff --git a/Samples/login.php b/Samples/login.php index 11f844f0..f2d020a7 100644 --- a/Samples/login.php +++ b/Samples/login.php @@ -32,7 +32,7 @@ * on its user interfaces. The implementation does not have to be in a single function like this -- it can be inlined * with the calling code, or live elsewhere. The TAN/confirmation can be obtained while the same PHP script is still * running (i.e. handleStrongAuthentication() is a blocking function that only returns once the authentication is done, - * which is useful for a CLI application), but it is also possible to interrupt the PHP execution entirely while asking + * which is useful for a CLI application), but it is also possible to interrupt the PHP process entirely while asking * for the TAN/confirmation and resume it later (which is useful for a web application). * * @param \Fhp\BaseAction $action Some action that requires strong authentication. @@ -51,6 +51,7 @@ function handleStrongAuthentication(\Fhp\BaseAction $action) /** * This function handles strong authentication for the case where the user needs to enter a TAN into the PHP * application. + * * @param \Fhp\BaseAction $action Some action that requires a TAN. * @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details. */ @@ -93,7 +94,7 @@ function handleTan(Fhp\BaseAction $action) // Optional: Instead of printing the above to the console, you can relay the information (challenge and TAN medium) // to the user in any other way (through your REST API, a push notification, ...). If waiting for the TAN requires - // you to interrupt this PHP execution and the TAN will arrive in a fresh (HTTP/REST/...) request, you can do so: + // you to interrupt this PHP process and the TAN will arrive in a fresh (HTTP/REST/...) request, you can do so: if ($optionallyPersistEverything = false) { $persistedAction = serialize($action); $persistedFints = $fints->persist(); @@ -116,7 +117,7 @@ function handleTan(Fhp\BaseAction $action) echo "Please enter the TAN:\n"; $tan = trim(fgets(STDIN)); - // Optional: If the state was persisted above, we can restore it now (imagine this is a new PHP execution). + // Optional: If the state was persisted above, we can restore it now (imagine this is a new PHP process). if ($optionallyPersistEverything) { $restoredState = file_get_contents(__DIR__ . '/state.txt'); list($persistedInstance, $persistedAction) = unserialize($restoredState); @@ -130,14 +131,15 @@ function handleTan(Fhp\BaseAction $action) /** * This function handles strong authentication for the case where the user needs to confirm the action on another - * device. Note: Depending on the banks you need compatibility with you may not need to implement decoupled - * authentication at all, i.e. you could filter out any decoupled TanModes when letting the user choose. + * device. Note: Depending on the banks you need compatibility with, you may not need to implement decoupled + * authentication at all, i.e., you could filter out any decoupled TanModes when letting the user choose. + * * @param \Fhp\BaseAction $action Some action that requires decoupled authentication. * @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details. */ function handleDecoupled(Fhp\BaseAction $action) { - global $fints; + global $fints, $options, $credentials; $tanMode = $fints->getSelectedTanMode(); $tanRequest = $action->getTanRequest(); @@ -150,6 +152,18 @@ function handleDecoupled(Fhp\BaseAction $action) echo 'Please check this device: ' . $tanRequest->getTanMediumName() . "\n"; } + // Just like in handleTan() above, we have the option to interrupt the PHP process at this point. In fact, the + // for-loop below that deals with the polling may even be running on the client side of your larger application, + // polling your application server regularly, which spawns a new PHP process each time. Here, we demonstrate this by + // persisting the instance to a local file and restoring it (even though that's not technically necessary for a + // single-process CLI script like this). + if ($optionallyPersistEverything = false) { + $persistedAction = serialize($action); + $persistedFints = $fints->persist(); + // See handleTan() for how to deal with this in practice. + file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction])); + } + // IMPORTANT: In your real application, you don't have to use sleep() in PHP. You can persist the state in the same // way as in handleTan() and restore it later. This allows you to use some other timer mechanism (e.g. in the user's // browser). This PHP sample code just serves to show the *logic* of the polling. Alternatively, you can even do @@ -162,22 +176,44 @@ function handleDecoupled(Fhp\BaseAction $action) $tanMode->getMaxDecoupledChecks() === 0 || $attempt < $tanMode->getMaxDecoupledChecks(); ++$attempt ) { + // Optional: If the state was persisted above, we can restore it now (imagine this is a new PHP process). + if ($optionallyPersistEverything) { + $restoredState = file_get_contents(__DIR__ . '/state.txt'); + list($persistedInstance, $persistedAction) = unserialize($restoredState); + $fints = \Fhp\FinTs::new($options, $credentials, $persistedInstance); + $action = unserialize($persistedAction); + } + + // Check if we're done. if ($fints->checkDecoupledSubmission($action)) { echo "Confirmed.\n"; return; } echo "Still waiting...\n"; + + // THIS IS CRUCIAL if you're using persistence in between polls. You must re-persist() the instance after + // calling checkDecoupledSubmission() and before calling it the next time. Don't reuse the + // $persistedInstance from above multiple times. + if ($optionallyPersistEverything) { + $persistedAction = serialize($action); + $persistedFints = $fints->persist(); + file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction])); + } + sleep($tanMode->getPeriodicDecoupledCheckDelaySeconds()); } throw new RuntimeException("Not confirmed after $attempt attempts, which is the limit."); } elseif ($tanMode->allowsManualConfirmation()) { - do { - echo "Please type 'done' and hit Return when you've completed the authentication on the other device.\n"; - while (trim(fgets(STDIN)) !== 'done') { - echo "Try again.\n"; - } - echo "Confirming that the action is done.\n"; - } while (!$fints->checkDecoupledSubmission($action)); + echo "Please type 'done' and hit Return when you've completed the authentication on the other device.\n"; + while (trim(fgets(STDIN)) !== 'done') { + echo "Try again.\n"; + } + echo "Confirming that the action is done.\n"; + if (!$fints->checkDecoupledSubmission($action)) { + throw new RuntimeException( + "You confirmed that the authentication for action was copmleted, but the server does not think so." + ); + } echo "Confirmed\n"; } else { throw new AssertionError('Server allows neither automated polling nor manual confirmation'); From 376bb31f62a4205d64691f7007ee64a2008c6794 Mon Sep 17 00:00:00 2001 From: Leo Beal Date: Tue, 26 Aug 2025 23:21:53 +0200 Subject: [PATCH 07/19] adds a failing test --- .../Fhp/Unit/SendSEPADirectDebitTest.php | 21 + lib/Tests/resources/pain.008.002.02.xml | 1477 +++++++++++++++++ 2 files changed, 1498 insertions(+) create mode 100644 lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php create mode 100644 lib/Tests/resources/pain.008.002.02.xml diff --git a/lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php b/lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php new file mode 100644 index 00000000..269ceddc --- /dev/null +++ b/lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php @@ -0,0 +1,21 @@ + + + + + FAKEPAIN397948648101 + 2025-08-26T21:03:32Z + 300 + 14380.00 + + + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + PMT001 + DD + true + 1 + 50.00 + + SEPA + CORE + FRST + + 2025-08-30 + + Example Sports Club e.V. + + + DE11123456789012345678 + + FAKEDEFFXXX + + DE99ZZZ00000000001SEPA + + + INV001 + 50.00 + + MANDATE0012025-08-01 + + FAKEDEPPXXX + Max Mustermann + DE44123456789012345678 + Membership Fee August + + + + PMT002 + DD + true + 1 + 75.00 + + SEPA + CORE + RCUR + + 2025-08-30 + Example Sports Club e.V. + DE11123456789012345678 + FAKEDEFFXXX + + INV002 + 75.00 + + MANDATE0022024-09-15 + + FAKEXYZZXXX + Jane Doe + DE55123456789012345678 + Quarterly Training Fee + + + + \ No newline at end of file From 2c2a6afe7de55a22504183c71beeaea18172d9ae Mon Sep 17 00:00:00 2001 From: Leo Beal Date: Tue, 26 Aug 2025 23:34:12 +0200 Subject: [PATCH 08/19] tests workflow --- .github/workflows/tests.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..b7872dc0 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,36 @@ +# .github/workflows/tests.yml +name: tests + +on: + push: + branches: + - master + pull_request: + +jobs: + phpunit: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: mbstring + + - name: Validate composer.json + run: composer validate --strict + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Run PHPUnit + run: ./vendor/bin/phpunit From dedb2b76e4bc05a10ff754c91f2e4556ad7c1d65 Mon Sep 17 00:00:00 2001 From: Leo Beal Date: Tue, 26 Aug 2025 23:40:09 +0200 Subject: [PATCH 09/19] Introduces lazy quantifiers --- lib/Fhp/Action/SendSEPADirectDebit.php | 4 ++-- lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Fhp/Action/SendSEPADirectDebit.php b/lib/Fhp/Action/SendSEPADirectDebit.php index 6452c508..ad60ec42 100644 --- a/lib/Fhp/Action/SendSEPADirectDebit.php +++ b/lib/Fhp/Action/SendSEPADirectDebit.php @@ -55,11 +55,11 @@ public static function create(SEPAAccount $account, string $painMessage, bool $t $nbOfTxs = substr_count($painMessage, ''); $ctrlSum = null; - if (preg_match('@.*(?[.0-9]+).*@s', $painMessage, $matches) === 1) { + if (preg_match('@.*?(?[0-9.]+).*?@s', $painMessage, $matches) === 1) { $ctrlSum = $matches['ctrlsum']; } - if (preg_match('@.*.*(?CORE|COR1|B2B).*.*@s', $painMessage, $matches) === 1) { + if (preg_match('@.*?.*?(?CORE|COR1|B2B).*?.*?@s', $painMessage, $matches) === 1) { $coreType = $matches['coretype']; } else { throw new \InvalidArgumentException('The type CORE/COR1/B2B is missing in PAIN message'); diff --git a/lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php b/lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php index 269ceddc..96d7d827 100644 --- a/lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php +++ b/lib/Tests/Fhp/Unit/SendSEPADirectDebitTest.php @@ -15,7 +15,8 @@ public function testCanSendLargeFiles() $painString = file_get_contents(__DIR__ . '/../../resources/pain.008.002.02.xml'); - //this will throw an error - SendSEPADirectDebit::create($account, $painString); + $sepa = SendSEPADirectDebit::create($account, $painString); + + $this->assertInstanceOf(SendSEPADirectDebit::class, $sepa); } } From 5713e50efec9e0467a0c38177858e90491663cac Mon Sep 17 00:00:00 2001 From: Leo Beal Date: Wed, 27 Aug 2025 00:00:02 +0200 Subject: [PATCH 10/19] Allow minor version update on composer --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 03c9e595..25d2c7fb 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,9 @@ "ext-mbstring": "*" }, "require-dev": { - "phpunit/phpunit": "9.5.*", - "php-mock/php-mock-phpunit": "2.6.*", - "friendsofphp/php-cs-fixer": "3.*" + "phpunit/phpunit": "^9.5", + "php-mock/php-mock-phpunit": "^2.6", + "friendsofphp/php-cs-fixer": "^3.0" }, "suggest": { "monolog/monolog": "Allow sending log messages to a variety of different handlers", From 1fb1c2674a195f842ef027fea1683e25fb57e9dc Mon Sep 17 00:00:00 2001 From: Leo Beal Date: Wed, 27 Aug 2025 00:47:34 +0200 Subject: [PATCH 11/19] removes composer validate --- .github/workflows/tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b7872dc0..182c67c7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,9 +26,6 @@ jobs: php-version: ${{ matrix.php }} extensions: mbstring - - name: Validate composer.json - run: composer validate --strict - - name: Install dependencies run: composer install --no-interaction --prefer-dist --no-progress From d94129dab0e5599c636ef99c91994afc1dd3c808 Mon Sep 17 00:00:00 2001 From: ampaze Date: Mon, 1 Sep 2025 12:19:59 +0200 Subject: [PATCH 12/19] Better support for new pain formats --- lib/Fhp/Action/SendSEPARealtimeTransfer.php | 23 +++++++++++++++++---- lib/Fhp/Action/SendSEPATransfer.php | 16 +++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/Fhp/Action/SendSEPARealtimeTransfer.php b/lib/Fhp/Action/SendSEPARealtimeTransfer.php index 1180124f..af790837 100644 --- a/lib/Fhp/Action/SendSEPARealtimeTransfer.php +++ b/lib/Fhp/Action/SendSEPARealtimeTransfer.php @@ -58,10 +58,25 @@ protected function createRequest(BPD $bpd, ?UPD $upd) /** @var HIIPZSv1|HIIPZSv2 $hiipzs */ $hiipzs = $bpd->requireLatestSupportedParameters('HIIPZS'); - /** @var HISPAS $hispas */ - $hispas = $bpd->requireLatestSupportedParameters('HISPAS'); - $supportedSchemas = $hispas->getParameter()->getUnterstuetzteSepaDatenformate(); - if (!in_array($this->xmlSchema, $supportedSchemas)) { + $supportedSchemas = $hiipzs->parameter->unterstuetzteSEPADatenformate; + + // If there are no SEPA formats available in the HIIPZS Parameters, we look to the general formats + if (is_null($supportedSchemas)) { + /** @var HISPAS $hispas */ + $hispas = $bpd->requireLatestSupportedParameters('HISPAS'); + $supportedSchemas = $hispas->getParameter()->getUnterstuetzteSepaDatenformate(); + } + + // Sometimes the Bank reports supported schemas with a "_GBIC_X" prefix. + // GIBC_X stands for German Banking Industry Committee and a version counter. + $xmlSchema = $this->xmlSchema; + $matchingSchemas = array_filter($supportedSchemas, function($key) use ($xmlSchema) { + // For example urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 from the xml matches + // urn:iso:std:iso:20022:tech:xsd:pain.001.001.09_GBIC_4 + return strpos($key, $xmlSchema) === 0; + }, ARRAY_FILTER_USE_KEY); + + if (count($matchingSchemas) > 0) { throw new UnsupportedException("The bank does not support the XML schema $this->xmlSchema, but only " . implode(', ', $supportedSchemas)); } diff --git a/lib/Fhp/Action/SendSEPATransfer.php b/lib/Fhp/Action/SendSEPATransfer.php index 463379d1..530983fc 100644 --- a/lib/Fhp/Action/SendSEPATransfer.php +++ b/lib/Fhp/Action/SendSEPATransfer.php @@ -52,13 +52,13 @@ protected function createRequest(BPD $bpd, ?UPD $upd) $numberOfTransactions = $xmlAsObject->CstmrCdtTrfInitn->GrpHdr->NbOfTxs; $hasReqdExDates = false; foreach ($xmlAsObject->CstmrCdtTrfInitn?->PmtInf as $pmtInfo) { - if (isset($pmtInfo->ReqdExctnDt) && $pmtInfo->ReqdExctnDt != '1999-01-01') { + // Checks for both, 1999-01-01 and
1999-01-01
+ if (isset($pmtInfo->ReqdExctnDt) && ($pmtInfo->ReqdExctnDt->Dt ?? $pmtInfo->ReqdExctnDt) != '1999-01-01') { $hasReqdExDates = true; break; } } - //NOW READ OUT, WICH SEGMENT SHOULD BE USED: if ($numberOfTransactions > 1 && $hasReqdExDates) { @@ -89,7 +89,17 @@ protected function createRequest(BPD $bpd, ?UPD $upd) /** @var HISPAS $hispas */ $parameters = $bpd->requireLatestSupportedParameters('HISPAS'); $supportedSchemas = $parameters->getParameter()->getUnterstuetzteSepaDatenformate(); - if (!in_array($this->xmlSchema, $supportedSchemas)) { + + // Sometimes the Bank reports supported schemas with a "_GBIC_X" prefix. + // GIBC_X stands for German Banking Industry Committee and a version counter. + $xmlSchema = $this->xmlSchema; + $matchingSchemas = array_filter($supportedSchemas, function($key) use ($xmlSchema) { + // For example urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 from the xml matches + // urn:iso:std:iso:20022:tech:xsd:pain.001.001.09_GBIC_4 + return strpos($key, $xmlSchema) === 0; + }, ARRAY_FILTER_USE_KEY); + + if (count($matchingSchemas) > 0) { throw new UnsupportedException("The bank does not support the XML schema $this->xmlSchema, but only " . implode(', ', $supportedSchemas)); } From 7af5869b226e028a2fc7bd19bb94c438ccd18530 Mon Sep 17 00:00:00 2001 From: ampaze Date: Mon, 1 Sep 2025 12:38:38 +0200 Subject: [PATCH 13/19] Better support for new pain formats --- lib/Fhp/Action/SendSEPADirectDebit.php | 11 ++++++++++- lib/Fhp/Action/SendSEPARealtimeTransfer.php | 4 ++-- lib/Fhp/Action/SendSEPATransfer.php | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/Fhp/Action/SendSEPADirectDebit.php b/lib/Fhp/Action/SendSEPADirectDebit.php index 6452c508..a4dcbe6e 100644 --- a/lib/Fhp/Action/SendSEPADirectDebit.php +++ b/lib/Fhp/Action/SendSEPADirectDebit.php @@ -148,7 +148,16 @@ protected function createRequest(BPD $bpd, ?UPD $upd) $supportedPainNamespaces = $hispas->getParameter()->getUnterstuetzteSepaDatenformate(); } - if (!in_array($this->painNamespace, $supportedPainNamespaces)) { + // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. + // GIBC_X stands for German Banking Industry Committee and a version counter. + $xmlSchema = $this->painNamespace; + $matchingSchemas = array_filter($supportedPainNamespaces, function($key) use ($xmlSchema) { + // For example urn:iso:std:iso:20022:tech:xsd:pain.008.001.08 from the xml matches + // urn:iso:std:iso:20022:tech:xsd:pain.008.001.08_GBIC_4 + return strpos($key, $xmlSchema) === 0; + }, ARRAY_FILTER_USE_KEY); + + if (count($matchingSchemas) > 0) { throw new UnsupportedException("The bank does not support the XML schema $this->painNamespace, but only " . implode(', ', $supportedPainNamespaces)); } diff --git a/lib/Fhp/Action/SendSEPARealtimeTransfer.php b/lib/Fhp/Action/SendSEPARealtimeTransfer.php index af790837..75eaaf2a 100644 --- a/lib/Fhp/Action/SendSEPARealtimeTransfer.php +++ b/lib/Fhp/Action/SendSEPARealtimeTransfer.php @@ -67,8 +67,8 @@ protected function createRequest(BPD $bpd, ?UPD $upd) $supportedSchemas = $hispas->getParameter()->getUnterstuetzteSepaDatenformate(); } - // Sometimes the Bank reports supported schemas with a "_GBIC_X" prefix. - // GIBC_X stands for German Banking Industry Committee and a version counter. + // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. + // GIBC_X stands for German Banking Industry Committee and a version counter. $xmlSchema = $this->xmlSchema; $matchingSchemas = array_filter($supportedSchemas, function($key) use ($xmlSchema) { // For example urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 from the xml matches diff --git a/lib/Fhp/Action/SendSEPATransfer.php b/lib/Fhp/Action/SendSEPATransfer.php index 530983fc..74ca31b6 100644 --- a/lib/Fhp/Action/SendSEPATransfer.php +++ b/lib/Fhp/Action/SendSEPATransfer.php @@ -90,8 +90,8 @@ protected function createRequest(BPD $bpd, ?UPD $upd) $parameters = $bpd->requireLatestSupportedParameters('HISPAS'); $supportedSchemas = $parameters->getParameter()->getUnterstuetzteSepaDatenformate(); - // Sometimes the Bank reports supported schemas with a "_GBIC_X" prefix. - // GIBC_X stands for German Banking Industry Committee and a version counter. + // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. + // GIBC_X stands for German Banking Industry Committee and a version counter. $xmlSchema = $this->xmlSchema; $matchingSchemas = array_filter($supportedSchemas, function($key) use ($xmlSchema) { // For example urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 from the xml matches From 43d321c9da09685d6f1fc79298e767a890be2337 Mon Sep 17 00:00:00 2001 From: ampaze Date: Tue, 2 Sep 2025 09:12:00 +0200 Subject: [PATCH 14/19] Better support for new pain formats --- lib/Fhp/Action/SendSEPADirectDebit.php | 8 ++++---- lib/Fhp/Action/SendSEPARealtimeTransfer.php | 8 ++++---- lib/Fhp/Action/SendSEPATransfer.php | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Fhp/Action/SendSEPADirectDebit.php b/lib/Fhp/Action/SendSEPADirectDebit.php index a4dcbe6e..b4ea99cb 100644 --- a/lib/Fhp/Action/SendSEPADirectDebit.php +++ b/lib/Fhp/Action/SendSEPADirectDebit.php @@ -151,13 +151,13 @@ protected function createRequest(BPD $bpd, ?UPD $upd) // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. // GIBC_X stands for German Banking Industry Committee and a version counter. $xmlSchema = $this->painNamespace; - $matchingSchemas = array_filter($supportedPainNamespaces, function($key) use ($xmlSchema) { + $matchingSchemas = array_filter($supportedPainNamespaces, function($value) use ($xmlSchema) { // For example urn:iso:std:iso:20022:tech:xsd:pain.008.001.08 from the xml matches // urn:iso:std:iso:20022:tech:xsd:pain.008.001.08_GBIC_4 - return strpos($key, $xmlSchema) === 0; - }, ARRAY_FILTER_USE_KEY); + return str_starts_with($value, $xmlSchema); + }); - if (count($matchingSchemas) > 0) { + if (count($matchingSchemas) === 0) { throw new UnsupportedException("The bank does not support the XML schema $this->painNamespace, but only " . implode(', ', $supportedPainNamespaces)); } diff --git a/lib/Fhp/Action/SendSEPARealtimeTransfer.php b/lib/Fhp/Action/SendSEPARealtimeTransfer.php index 75eaaf2a..1dafb8b2 100644 --- a/lib/Fhp/Action/SendSEPARealtimeTransfer.php +++ b/lib/Fhp/Action/SendSEPARealtimeTransfer.php @@ -70,13 +70,13 @@ protected function createRequest(BPD $bpd, ?UPD $upd) // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. // GIBC_X stands for German Banking Industry Committee and a version counter. $xmlSchema = $this->xmlSchema; - $matchingSchemas = array_filter($supportedSchemas, function($key) use ($xmlSchema) { + $matchingSchemas = array_filter($supportedSchemas, function($value) use ($xmlSchema) { // For example urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 from the xml matches // urn:iso:std:iso:20022:tech:xsd:pain.001.001.09_GBIC_4 - return strpos($key, $xmlSchema) === 0; - }, ARRAY_FILTER_USE_KEY); + return str_starts_with($value, $xmlSchema); + }); - if (count($matchingSchemas) > 0) { + if (count($matchingSchemas) === 0) { throw new UnsupportedException("The bank does not support the XML schema $this->xmlSchema, but only " . implode(', ', $supportedSchemas)); } diff --git a/lib/Fhp/Action/SendSEPATransfer.php b/lib/Fhp/Action/SendSEPATransfer.php index 74ca31b6..477bcbeb 100644 --- a/lib/Fhp/Action/SendSEPATransfer.php +++ b/lib/Fhp/Action/SendSEPATransfer.php @@ -93,13 +93,13 @@ protected function createRequest(BPD $bpd, ?UPD $upd) // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. // GIBC_X stands for German Banking Industry Committee and a version counter. $xmlSchema = $this->xmlSchema; - $matchingSchemas = array_filter($supportedSchemas, function($key) use ($xmlSchema) { + $matchingSchemas = array_filter($supportedSchemas, function($value) use ($xmlSchema) { // For example urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 from the xml matches // urn:iso:std:iso:20022:tech:xsd:pain.001.001.09_GBIC_4 - return strpos($key, $xmlSchema) === 0; - }, ARRAY_FILTER_USE_KEY); + return str_starts_with($value, $xmlSchema); + }); - if (count($matchingSchemas) > 0) { + if (count($matchingSchemas) === 0) { throw new UnsupportedException("The bank does not support the XML schema $this->xmlSchema, but only " . implode(', ', $supportedSchemas)); } From 01b29609f8232774b92c3580fd6677d3ad16e6d4 Mon Sep 17 00:00:00 2001 From: ampaze Date: Wed, 3 Sep 2025 12:30:32 +0200 Subject: [PATCH 15/19] Use the UnterstuetzteSEPADatenformateTrait in all Actions to avoid pain namespace missmatches --- lib/Fhp/Action/SendSEPADirectDebit.php | 4 ++-- lib/Fhp/Action/SendSEPARealtimeTransfer.php | 4 ++-- lib/Fhp/Action/SendSEPATransfer.php | 4 ++-- ...minierteSEPAFirmenSammellastschriftEinreichenV2.php | 6 +++++- ...minierteSEPAFirmenEinzellastschriftEinreichenV2.php | 7 ++++++- ...terTerminierteSEPASammellastschriftEinreichenV2.php | 6 +++++- lib/Fhp/Segment/DSE/HIDSESv1.php | 2 +- lib/Fhp/Segment/DSE/HIDSESv2.php | 2 +- ...terTerminierteSEPAEinzellastschriftEinreichenV2.php | 7 ++++++- .../IPZ/ParameterSEPAInstantPaymentZahlungV1.php | 6 +++++- .../IPZ/ParameterSEPAInstantPaymentZahlungV2.php | 6 +++++- .../SPA/ParameterSepaKontoverbindungAnfordern.php | 7 ++++--- .../SPA/ParameterSepaKontoverbindungAnfordernV1.php | 3 ++- .../SPA/ParameterSepaKontoverbindungAnfordernV2.php | 3 ++- .../SPA/ParameterSepaKontoverbindungAnfordernV3.php | 3 ++- lib/Fhp/Segment/UnterstuetzteSEPADatenformate.php | 10 ++++++++++ ...rait.php => UnterstuetzteSEPADatenformateTrait.php} | 8 ++++---- 17 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 lib/Fhp/Segment/UnterstuetzteSEPADatenformate.php rename lib/Fhp/Segment/{SPA/GetUnterstuetzteSepaDatenformateTrait.php => UnterstuetzteSEPADatenformateTrait.php} (73%) diff --git a/lib/Fhp/Action/SendSEPADirectDebit.php b/lib/Fhp/Action/SendSEPADirectDebit.php index b2c8d8b8..1c9fe599 100644 --- a/lib/Fhp/Action/SendSEPADirectDebit.php +++ b/lib/Fhp/Action/SendSEPADirectDebit.php @@ -138,14 +138,14 @@ protected function createRequest(BPD $bpd, ?UPD $upd) if ($hidxes->getVersion() === 2) { /** @var HIDMESv2|HIDSESv2 $hidxes */ - $supportedPainNamespaces = $hidxes->getParameter()->unterstuetzteSEPADatenformate; + $supportedPainNamespaces = $hidxes->getParameter()->getUnterstuetzteSEPADatenformate(); } // If there are no SEPA formats available in the HIDXES Parameters, we look to the general formats if (!is_array($supportedPainNamespaces) || count($supportedPainNamespaces) === 0) { /** @var HISPAS $hispas */ $hispas = $bpd->requireLatestSupportedParameters('HISPAS'); - $supportedPainNamespaces = $hispas->getParameter()->getUnterstuetzteSepaDatenformate(); + $supportedPainNamespaces = $hispas->getParameter()->getUnterstuetzteSEPADatenformate(); } // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. diff --git a/lib/Fhp/Action/SendSEPARealtimeTransfer.php b/lib/Fhp/Action/SendSEPARealtimeTransfer.php index 1dafb8b2..22dfda31 100644 --- a/lib/Fhp/Action/SendSEPARealtimeTransfer.php +++ b/lib/Fhp/Action/SendSEPARealtimeTransfer.php @@ -58,13 +58,13 @@ protected function createRequest(BPD $bpd, ?UPD $upd) /** @var HIIPZSv1|HIIPZSv2 $hiipzs */ $hiipzs = $bpd->requireLatestSupportedParameters('HIIPZS'); - $supportedSchemas = $hiipzs->parameter->unterstuetzteSEPADatenformate; + $supportedSchemas = $hiipzs->parameter->getUnterstuetzteSEPADatenformate(); // If there are no SEPA formats available in the HIIPZS Parameters, we look to the general formats if (is_null($supportedSchemas)) { /** @var HISPAS $hispas */ $hispas = $bpd->requireLatestSupportedParameters('HISPAS'); - $supportedSchemas = $hispas->getParameter()->getUnterstuetzteSepaDatenformate(); + $supportedSchemas = $hispas->getParameter()->getUnterstuetzteSEPADatenformate(); } // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. diff --git a/lib/Fhp/Action/SendSEPATransfer.php b/lib/Fhp/Action/SendSEPATransfer.php index 477bcbeb..68de23bf 100644 --- a/lib/Fhp/Action/SendSEPATransfer.php +++ b/lib/Fhp/Action/SendSEPATransfer.php @@ -87,8 +87,8 @@ protected function createRequest(BPD $bpd, ?UPD $upd) } /** @var HISPAS $hispas */ - $parameters = $bpd->requireLatestSupportedParameters('HISPAS'); - $supportedSchemas = $parameters->getParameter()->getUnterstuetzteSepaDatenformate(); + $hispas = $bpd->requireLatestSupportedParameters('HISPAS'); + $supportedSchemas = $hispas->getParameter()->getUnterstuetzteSEPADatenformate(); // Sometimes the Bank reports supported schemas with a "_GBIC_X" postfix. // GIBC_X stands for German Banking Industry Committee and a version counter. diff --git a/lib/Fhp/Segment/BME/ParameterTerminierteSEPAFirmenSammellastschriftEinreichenV2.php b/lib/Fhp/Segment/BME/ParameterTerminierteSEPAFirmenSammellastschriftEinreichenV2.php index 34f76f12..e4ed651d 100644 --- a/lib/Fhp/Segment/BME/ParameterTerminierteSEPAFirmenSammellastschriftEinreichenV2.php +++ b/lib/Fhp/Segment/BME/ParameterTerminierteSEPAFirmenSammellastschriftEinreichenV2.php @@ -3,9 +3,13 @@ namespace Fhp\Segment\BME; use Fhp\Segment\BSE\ParameterTerminierteSEPAFirmenLastschriftEinreichenV2; +use Fhp\Segment\UnterstuetzteSEPADatenformate; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; -class ParameterTerminierteSEPAFirmenSammellastschriftEinreichenV2 extends ParameterTerminierteSEPAFirmenLastschriftEinreichenV2 +class ParameterTerminierteSEPAFirmenSammellastschriftEinreichenV2 extends ParameterTerminierteSEPAFirmenLastschriftEinreichenV2 implements UnterstuetzteSEPADatenformate { + use UnterstuetzteSEPADatenformateTrait; + public int $maximaleAnzahlDirectDebitTransferTransactionInformation; public bool $summenfeldBenoetigt; public bool $einzelbuchungErlaubt; diff --git a/lib/Fhp/Segment/BSE/ParameterTerminierteSEPAFirmenEinzellastschriftEinreichenV2.php b/lib/Fhp/Segment/BSE/ParameterTerminierteSEPAFirmenEinzellastschriftEinreichenV2.php index ee09677d..dc256afe 100644 --- a/lib/Fhp/Segment/BSE/ParameterTerminierteSEPAFirmenEinzellastschriftEinreichenV2.php +++ b/lib/Fhp/Segment/BSE/ParameterTerminierteSEPAFirmenEinzellastschriftEinreichenV2.php @@ -2,8 +2,13 @@ namespace Fhp\Segment\BSE; -class ParameterTerminierteSEPAFirmenEinzellastschriftEinreichenV2 extends ParameterTerminierteSEPAFirmenLastschriftEinreichenV2 +use Fhp\Segment\UnterstuetzteSEPADatenformate; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; + +class ParameterTerminierteSEPAFirmenEinzellastschriftEinreichenV2 extends ParameterTerminierteSEPAFirmenLastschriftEinreichenV2 implements UnterstuetzteSEPADatenformate { + use UnterstuetzteSEPADatenformateTrait; + /** Max Length: 4096 */ public ?string $zulaessigePurposecodes = null; diff --git a/lib/Fhp/Segment/DME/ParameterTerminierteSEPASammellastschriftEinreichenV2.php b/lib/Fhp/Segment/DME/ParameterTerminierteSEPASammellastschriftEinreichenV2.php index 02070597..ebaa9f46 100644 --- a/lib/Fhp/Segment/DME/ParameterTerminierteSEPASammellastschriftEinreichenV2.php +++ b/lib/Fhp/Segment/DME/ParameterTerminierteSEPASammellastschriftEinreichenV2.php @@ -3,9 +3,13 @@ namespace Fhp\Segment\DME; use Fhp\Segment\DSE\ParameterTerminierteSEPALastschriftEinreichenV2; +use Fhp\Segment\UnterstuetzteSEPADatenformate; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; -class ParameterTerminierteSEPASammellastschriftEinreichenV2 extends ParameterTerminierteSEPALastschriftEinreichenV2 +class ParameterTerminierteSEPASammellastschriftEinreichenV2 extends ParameterTerminierteSEPALastschriftEinreichenV2 implements UnterstuetzteSEPADatenformate { + use UnterstuetzteSEPADatenformateTrait; + public int $maximaleAnzahlDirectDebitTransferTransactionInformation; public bool $summenfeldBenoetigt; public bool $einzelbuchungErlaubt; diff --git a/lib/Fhp/Segment/DSE/HIDSESv1.php b/lib/Fhp/Segment/DSE/HIDSESv1.php index 9e77934c..9ee8b5b5 100644 --- a/lib/Fhp/Segment/DSE/HIDSESv1.php +++ b/lib/Fhp/Segment/DSE/HIDSESv1.php @@ -15,7 +15,7 @@ class HIDSESv1 extends BaseGeschaeftsvorfallparameter implements HIDXES { public ParameterTerminierteSEPAEinzellastschriftEinreichenV1 $parameter; - public function getParameter(): SEPADirectDebitMinimalLeadTimeProvider + public function getParameter(): ParameterTerminierteSEPAEinzellastschriftEinreichenV1 { return $this->parameter; } diff --git a/lib/Fhp/Segment/DSE/HIDSESv2.php b/lib/Fhp/Segment/DSE/HIDSESv2.php index 17f45000..680d12f4 100644 --- a/lib/Fhp/Segment/DSE/HIDSESv2.php +++ b/lib/Fhp/Segment/DSE/HIDSESv2.php @@ -15,7 +15,7 @@ class HIDSESv2 extends BaseGeschaeftsvorfallparameter implements HIDXES { public ParameterTerminierteSEPAEinzellastschriftEinreichenV2 $parameter; - public function getParameter(): SEPADirectDebitMinimalLeadTimeProvider + public function getParameter(): ParameterTerminierteSEPAEinzellastschriftEinreichenV2 { return $this->parameter; } diff --git a/lib/Fhp/Segment/DSE/ParameterTerminierteSEPAEinzellastschriftEinreichenV2.php b/lib/Fhp/Segment/DSE/ParameterTerminierteSEPAEinzellastschriftEinreichenV2.php index f6a6f629..1123b5f1 100644 --- a/lib/Fhp/Segment/DSE/ParameterTerminierteSEPAEinzellastschriftEinreichenV2.php +++ b/lib/Fhp/Segment/DSE/ParameterTerminierteSEPAEinzellastschriftEinreichenV2.php @@ -2,8 +2,13 @@ namespace Fhp\Segment\DSE; -class ParameterTerminierteSEPAEinzellastschriftEinreichenV2 extends ParameterTerminierteSEPALastschriftEinreichenV2 +use Fhp\Segment\UnterstuetzteSEPADatenformate; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; + +class ParameterTerminierteSEPAEinzellastschriftEinreichenV2 extends ParameterTerminierteSEPALastschriftEinreichenV2 implements UnterstuetzteSEPADatenformate { + use UnterstuetzteSEPADatenformateTrait; + /** Max Length: 4096 */ public ?string $zulaessigePurposecodes = null; diff --git a/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV1.php b/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV1.php index a323cd94..9ae83ab2 100644 --- a/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV1.php +++ b/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV1.php @@ -3,6 +3,8 @@ namespace Fhp\Segment\IPZ; use Fhp\Segment\BaseDeg; +use Fhp\Segment\UnterstuetzteSEPADatenformate; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; /** * Parameter SEPA-Instant Payment Zahlung (Version 1) @@ -10,8 +12,10 @@ * @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Messages_Geschaeftsvorfaelle_2022-04-15_final_version.pdf * Section D */ -class ParameterSEPAInstantPaymentZahlungV1 extends BaseDeg +class ParameterSEPAInstantPaymentZahlungV1 extends BaseDeg implements UnterstuetzteSEPADatenformate { + use UnterstuetzteSEPADatenformateTrait; + /** Max Length: 4096 */ public ?string $zulaessigePurposecodes = null; diff --git a/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php b/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php index 70a290b5..6675d306 100644 --- a/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php +++ b/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php @@ -3,6 +3,8 @@ namespace Fhp\lib\Fhp\Segment\IPZ; use Fhp\Segment\BaseDeg; +use Fhp\Segment\UnterstuetzteSEPADatenformate; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; /** * Parameter SEPA-Instant Payment Zahlung (Version 2) @@ -10,8 +12,10 @@ * @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Messages_Geschaeftsvorfaelle_2022-04-15_final_version.pdf * Section D */ -class ParameterSEPAInstantPaymentZahlungV2 extends BaseDeg +class ParameterSEPAInstantPaymentZahlungV2 extends BaseDeg implements UnterstuetzteSEPADatenformate { + use UnterstuetzteSEPADatenformateTrait; + public bool $umwandlungNachSEPAUeberweisungZulaessigErlaubt; /** Max Length: 4096 */ diff --git a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordern.php b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordern.php index 1f2a5039..d0b09eb7 100644 --- a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordern.php +++ b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordern.php @@ -3,11 +3,12 @@ namespace Fhp\Segment\SPA; +use Fhp\Segment\UnterstuetzteSEPADatenformate; + /** * Data Element Group: Parameter SEPA-Kontoverbindung anfordern */ -interface ParameterSepaKontoverbindungAnfordern +interface ParameterSepaKontoverbindungAnfordern extends UnterstuetzteSEPADatenformate { - /** @return string[] */ - public function getUnterstuetzteSepaDatenformate(): array; + } diff --git a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV1.php b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV1.php index cc7586df..49e51bd6 100644 --- a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV1.php +++ b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV1.php @@ -4,6 +4,7 @@ namespace Fhp\Segment\SPA; use Fhp\Segment\BaseDeg; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; /** * Data Element Group: Parameter SEPA-Kontoverbindung anfordern (Version 1) @@ -13,7 +14,7 @@ */ class ParameterSepaKontoverbindungAnfordernV1 extends BaseDeg implements ParameterSepaKontoverbindungAnfordern { - use GetUnterstuetzteSepaDatenformateTrait; + use UnterstuetzteSEPADatenformateTrait; public bool $einzelkontenabrufErlaubt; public bool $nationaleKontoverbindungErlaubt; diff --git a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV2.php b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV2.php index e2ed1ea1..f82a95ec 100644 --- a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV2.php +++ b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV2.php @@ -4,6 +4,7 @@ namespace Fhp\Segment\SPA; use Fhp\Segment\BaseDeg; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; /** * Data Element Group: Parameter SEPA-Kontoverbindung anfordern (Version 2) @@ -13,7 +14,7 @@ */ class ParameterSepaKontoverbindungAnfordernV2 extends BaseDeg implements ParameterSepaKontoverbindungAnfordern { - use GetUnterstuetzteSepaDatenformateTrait; + use UnterstuetzteSEPADatenformateTrait; public bool $einzelkontenabrufErlaubt; public bool $nationaleKontoverbindungErlaubt; diff --git a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV3.php b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV3.php index 8dc1bbec..901a10a6 100644 --- a/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV3.php +++ b/lib/Fhp/Segment/SPA/ParameterSepaKontoverbindungAnfordernV3.php @@ -4,6 +4,7 @@ namespace Fhp\Segment\SPA; use Fhp\Segment\BaseDeg; +use Fhp\Segment\UnterstuetzteSEPADatenformateTrait; /** * Data Element Group: Parameter SEPA-Kontoverbindung anfordern (Version 3) @@ -13,7 +14,7 @@ */ class ParameterSepaKontoverbindungAnfordernV3 extends BaseDeg implements ParameterSepaKontoverbindungAnfordern { - use GetUnterstuetzteSepaDatenformateTrait; + use UnterstuetzteSEPADatenformateTrait; public bool $einzelkontenabrufErlaubt; public bool $nationaleKontoverbindungErlaubt; diff --git a/lib/Fhp/Segment/UnterstuetzteSEPADatenformate.php b/lib/Fhp/Segment/UnterstuetzteSEPADatenformate.php new file mode 100644 index 00000000..e3e5d1af --- /dev/null +++ b/lib/Fhp/Segment/UnterstuetzteSEPADatenformate.php @@ -0,0 +1,10 @@ + 'urn:iso:std:iso:20022:tech:xsd:', '.xsd' => '', ]); - }, $this->unterstuetzteSepaDatenformate); + }, $this->unterstuetzteSepaDatenformate ?? $this->unterstuetzteSEPADatenformate ?? []); } } From 9b8c0d3c7f3e56f5e6b6ea296e5ac0a9b7a4e30b Mon Sep 17 00:00:00 2001 From: Mario Voigt Date: Wed, 8 Oct 2025 10:55:09 +0200 Subject: [PATCH 16/19] Update ParameterSEPAInstantPaymentZahlungV2.php --- lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php b/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php index 6675d306..4e40e3e4 100644 --- a/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php +++ b/lib/Fhp/Segment/IPZ/ParameterSEPAInstantPaymentZahlungV2.php @@ -1,6 +1,6 @@ Date: Wed, 8 Oct 2025 10:55:23 +0200 Subject: [PATCH 17/19] Update HIIPZSv2.php --- lib/Fhp/Segment/IPZ/HIIPZSv2.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Segment/IPZ/HIIPZSv2.php b/lib/Fhp/Segment/IPZ/HIIPZSv2.php index 18a55f34..e78c92ba 100644 --- a/lib/Fhp/Segment/IPZ/HIIPZSv2.php +++ b/lib/Fhp/Segment/IPZ/HIIPZSv2.php @@ -2,7 +2,7 @@ namespace Fhp\Segment\IPZ; -use Fhp\lib\Fhp\Segment\IPZ\ParameterSEPAInstantPaymentZahlungV2; +use Fhp\Segment\IPZ\ParameterSEPAInstantPaymentZahlungV2; use Fhp\Segment\BaseGeschaeftsvorfallparameter; use Fhp\Segment\BaseSegment; From 9b767345f56a11dd359cd5b68d44e56b893cc294 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Mon, 13 Oct 2025 21:57:10 +0200 Subject: [PATCH 18/19] Remove @inheritdoc annotations It's the latest PHP CS Fixer default: https://cs.symfony.com/doc/rules/phpdoc/no_superfluous_phpdoc_tags.html The reasoning that it's not useful for any IDEs or for PHP doc generation: https://www.drupal.org/project/coding_standards/issues/3335320 --- lib/Fhp/Action/GetBalance.php | 2 -- lib/Fhp/Action/GetDepotAufstellung.php | 2 -- lib/Fhp/Action/GetSEPAAccounts.php | 2 -- .../Action/GetSEPADirectDebitParameters.php | 1 - lib/Fhp/Action/GetStatementOfAccount.php | 2 -- lib/Fhp/Action/GetStatementOfAccountXML.php | 2 -- lib/Fhp/Action/SendSEPARealtimeTransfer.php | 2 -- lib/Fhp/Action/SendSEPATransfer.php | 2 -- lib/Fhp/MT940/Dialect/PostbankMT940.php | 1 - lib/Fhp/MT940/Dialect/SpardaMT940.php | 1 - lib/Fhp/Model/FlickerTan/StartCode.php | 9 --------- lib/Fhp/Model/NoPsd2TanMode.php | 17 ----------------- lib/Fhp/PaginateableAction.php | 6 ------ lib/Fhp/Protocol/DialogInitialization.php | 1 - lib/Fhp/Protocol/GetTanMedia.php | 2 -- lib/Fhp/Segment/Common/Kik.php | 1 - lib/Fhp/Segment/Common/Kti.php | 3 --- lib/Fhp/Segment/Common/Kto.php | 2 -- lib/Fhp/Segment/Common/KtvV3.php | 2 -- lib/Fhp/Segment/Common/Ktz.php | 2 -- .../HIUPD/ErlaubteGeschaeftsvorfaelleV1.php | 1 - .../HIUPD/ErlaubteGeschaeftsvorfaelleV2.php | 1 - lib/Fhp/Segment/HIUPD/HIUPDv4.php | 2 -- lib/Fhp/Segment/HIUPD/HIUPDv6.php | 2 -- lib/Fhp/Segment/TAB/HITABv4.php | 1 - lib/Fhp/Segment/TAB/HITABv5.php | 1 - lib/Fhp/Segment/TAB/TanMediumListeV4.php | 2 -- lib/Fhp/Segment/TAB/TanMediumListeV5.php | 2 -- lib/Fhp/Segment/TAN/HITANv6.php | 3 --- ...fahrensparameterZweiSchrittVerfahrenV6.php | 19 ------------------- ...fahrensparameterZweiSchrittVerfahrenV7.php | 19 ------------------- lib/Tests/Fhp/FinTsPeer.php | 2 -- 32 files changed, 117 deletions(-) diff --git a/lib/Fhp/Action/GetBalance.php b/lib/Fhp/Action/GetBalance.php index 00c053f3..d0dab2d3 100644 --- a/lib/Fhp/Action/GetBalance.php +++ b/lib/Fhp/Action/GetBalance.php @@ -96,7 +96,6 @@ public function getBalances() return $this->response; } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { /** @var BaseSegment $hisals */ @@ -115,7 +114,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) } } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/Action/GetDepotAufstellung.php b/lib/Fhp/Action/GetDepotAufstellung.php index 3f32987d..0dadcda8 100644 --- a/lib/Fhp/Action/GetDepotAufstellung.php +++ b/lib/Fhp/Action/GetDepotAufstellung.php @@ -111,7 +111,6 @@ public function getDepotWert(): float return $this->depotWert; } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { /** @var HIWPDS $hiwpds */ @@ -125,7 +124,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) } } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/Action/GetSEPAAccounts.php b/lib/Fhp/Action/GetSEPAAccounts.php index 92e876dc..1101075e 100644 --- a/lib/Fhp/Action/GetSEPAAccounts.php +++ b/lib/Fhp/Action/GetSEPAAccounts.php @@ -47,7 +47,6 @@ public function getAccounts(): array return $this->accounts; } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { /** @var BaseSegment $hispas */ @@ -64,7 +63,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) } } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/Action/GetSEPADirectDebitParameters.php b/lib/Fhp/Action/GetSEPADirectDebitParameters.php index 55303179..9ef1e858 100644 --- a/lib/Fhp/Action/GetSEPADirectDebitParameters.php +++ b/lib/Fhp/Action/GetSEPADirectDebitParameters.php @@ -56,7 +56,6 @@ public static function getHixxesSegmentName(string $directDebitType, bool $singl } } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { $this->hidxes = $bpd->requireLatestSupportedParameters(static::getHixxesSegmentName($this->directDebitType, $this->singleDirectDebit)); diff --git a/lib/Fhp/Action/GetStatementOfAccount.php b/lib/Fhp/Action/GetStatementOfAccount.php index 8529ec5e..a8a7cf89 100644 --- a/lib/Fhp/Action/GetStatementOfAccount.php +++ b/lib/Fhp/Action/GetStatementOfAccount.php @@ -147,7 +147,6 @@ public function getStatement(): StatementOfAccount return $this->statement; } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { $this->bankName = $bpd->getBankName(); @@ -171,7 +170,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) } } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/Action/GetStatementOfAccountXML.php b/lib/Fhp/Action/GetStatementOfAccountXML.php index 71d20cbf..70563edd 100644 --- a/lib/Fhp/Action/GetStatementOfAccountXML.php +++ b/lib/Fhp/Action/GetStatementOfAccountXML.php @@ -115,7 +115,6 @@ public function getBookedXML(): array return $this->xml; } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { if ($upd === null) { @@ -149,7 +148,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) } } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/Action/SendSEPARealtimeTransfer.php b/lib/Fhp/Action/SendSEPARealtimeTransfer.php index 22dfda31..37a84ecc 100644 --- a/lib/Fhp/Action/SendSEPARealtimeTransfer.php +++ b/lib/Fhp/Action/SendSEPARealtimeTransfer.php @@ -52,7 +52,6 @@ public static function create(SEPAAccount $account, string $painMessage, bool $a return $result; } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { /** @var HIIPZSv1|HIIPZSv2 $hiipzs */ @@ -92,7 +91,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) return $hkipz; } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/Action/SendSEPATransfer.php b/lib/Fhp/Action/SendSEPATransfer.php index 68de23bf..cc2370d7 100644 --- a/lib/Fhp/Action/SendSEPATransfer.php +++ b/lib/Fhp/Action/SendSEPATransfer.php @@ -44,7 +44,6 @@ public static function create(SEPAAccount $account, string $painMessage): SendSE return $result; } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { //ANALYSE XML FOR RECEIPTS AND PAYMENT DATE @@ -110,7 +109,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) return $segment; } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/MT940/Dialect/PostbankMT940.php b/lib/Fhp/MT940/Dialect/PostbankMT940.php index b7d1e912..826c16fa 100644 --- a/lib/Fhp/MT940/Dialect/PostbankMT940.php +++ b/lib/Fhp/MT940/Dialect/PostbankMT940.php @@ -8,7 +8,6 @@ class PostbankMT940 extends MT940 { public const DIALECT_ID = 'https://hbci.postbank.de/banking/hbci.do'; - /** {@inheritdoc} */ public function extractStructuredDataFromRemittanceLines($descriptionLines, string &$gvc, array &$rawLines, array $transaction): array { // z.B bei Zinsen o.ä. ist alles leer diff --git a/lib/Fhp/MT940/Dialect/SpardaMT940.php b/lib/Fhp/MT940/Dialect/SpardaMT940.php index f1f70f4a..24b873b2 100644 --- a/lib/Fhp/MT940/Dialect/SpardaMT940.php +++ b/lib/Fhp/MT940/Dialect/SpardaMT940.php @@ -8,7 +8,6 @@ class SpardaMT940 extends MT940 { public const DIALECT_ID = 'https://fints.bankingonline.de/fints/FinTs30PinTanHttpGate'; - /** {@inheritdoc} */ public function extractStructuredDataFromRemittanceLines($descriptionLines, string &$gvc, array &$rawLines, array $transaction): array { $otherInfo = []; diff --git a/lib/Fhp/Model/FlickerTan/StartCode.php b/lib/Fhp/Model/FlickerTan/StartCode.php index faa0893e..bcce4389 100644 --- a/lib/Fhp/Model/FlickerTan/StartCode.php +++ b/lib/Fhp/Model/FlickerTan/StartCode.php @@ -68,17 +68,11 @@ protected function __construct(array $ctrlBytes, string $data) $this->headerHighBit = '1'; } - /** - * {@inheritDoc} - */ public function toHex(): string { return $this->getHeaderHex() . implode('', $this->controlBytes) . $this->getDataHex(); } - /** - * {@inheritDoc} - */ public function getLuhnChecksum(): int { $luhn = 0; @@ -89,9 +83,6 @@ public function getLuhnChecksum(): int return $luhn; } - /** - * {@inheritDoc} - */ public function __debugInfo(): ?array { return [ diff --git a/lib/Fhp/Model/NoPsd2TanMode.php b/lib/Fhp/Model/NoPsd2TanMode.php index 223722f2..c6ab6f60 100644 --- a/lib/Fhp/Model/NoPsd2TanMode.php +++ b/lib/Fhp/Model/NoPsd2TanMode.php @@ -16,13 +16,11 @@ final class NoPsd2TanMode implements TanMode { public const ID = -1; - /** {@inheritdoc} */ public function getId(): int { return self::ID; } - /** {@inheritdoc} */ public function getName(): string { return 'No PSD2/TANs supported'; @@ -33,91 +31,76 @@ public function isProzessvariante2(): bool return false; } - /** {@inheritdoc} */ public function isDecoupled(): bool { return false; } - /** {@inheritdoc} */ public function getChallengeLabel(): string { return ''; } - /** {@inheritdoc} */ public function getMaxChallengeLength(): int { return 0; } - /** {@inheritdoc} */ public function getMaxTanLength(): int { return 0; } - /** {@inheritdoc} */ public function getTanFormat(): int { return 0; } - /** {@inheritdoc} */ public function needsTanMedium(): bool { return false; } - /** {@inheritdoc} */ public function getSmsAbbuchungskontoErforderlich(): bool { return false; } - /** {@inheritdoc} */ public function getAuftraggeberkontoErforderlich(): bool { return false; } - /** {@inheritdoc} */ public function getChallengeKlasseErforderlich(): bool { return false; } - /** {@inheritdoc} */ public function getAntwortHhdUcErforderlich(): bool { return false; } - /** {@inheritdoc} */ public function getMaxDecoupledChecks(): int { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function getFirstDecoupledCheckDelaySeconds(): int { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function getPeriodicDecoupledCheckDelaySeconds(): int { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function allowsManualConfirmation(): bool { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function allowsAutomatedPolling(): bool { throw new \RuntimeException('Only allowed for decoupled TAN modes'); diff --git a/lib/Fhp/PaginateableAction.php b/lib/Fhp/PaginateableAction.php index b01d4807..8aeff872 100644 --- a/lib/Fhp/PaginateableAction.php +++ b/lib/Fhp/PaginateableAction.php @@ -34,8 +34,6 @@ abstract class PaginateableAction extends BaseAction /** * @deprecated Beginning from PHP7.4 __unserialize is used for new generated strings, then this method is only used for previously generated strings - remove after May 2023 - * - * {@inheritdoc} */ public function serialize(): string { @@ -53,8 +51,6 @@ public function __serialize(): array /** * @deprecated Beginning from PHP7.4 __unserialize is used for new generated strings, then this method is only used for previously generated strings - remove after May 2023 - * - * {@inheritdoc} */ public function unserialize($serialized) { @@ -82,7 +78,6 @@ public function hasMorePages(): bool return !$this->isDone() && $this->paginationToken !== null; } - /** {@inheritdoc} */ public function processResponse(Message $response) { if (($pagination = $response->findRueckmeldung(Rueckmeldungscode::PAGINATION)) !== null) { @@ -97,7 +92,6 @@ public function processResponse(Message $response) } } - /** {@inheritdoc} */ public function getNextRequest(?BPD $bpd, ?UPD $upd) { if ($this->requestSegments === null) { diff --git a/lib/Fhp/Protocol/DialogInitialization.php b/lib/Fhp/Protocol/DialogInitialization.php index 3e07aecb..2e1974f5 100644 --- a/lib/Fhp/Protocol/DialogInitialization.php +++ b/lib/Fhp/Protocol/DialogInitialization.php @@ -151,7 +151,6 @@ public function __unserialize(array $serialized): void parent::unserialize($parentSerialized); } - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { throw new \AssertionError('DialogInitialization::createRequest should not be used.'); diff --git a/lib/Fhp/Protocol/GetTanMedia.php b/lib/Fhp/Protocol/GetTanMedia.php index da0f32f3..4bb2a750 100644 --- a/lib/Fhp/Protocol/GetTanMedia.php +++ b/lib/Fhp/Protocol/GetTanMedia.php @@ -17,7 +17,6 @@ class GetTanMedia extends BaseAction /** @var TanMediumListe[]|null */ private $tanMedia; - /** {@inheritdoc} */ protected function createRequest(BPD $bpd, ?UPD $upd) { // Prepare the HKTAB request. @@ -32,7 +31,6 @@ protected function createRequest(BPD $bpd, ?UPD $upd) } } - /** {@inheritdoc} */ public function processResponse(Message $response) { parent::processResponse($response); diff --git a/lib/Fhp/Segment/Common/Kik.php b/lib/Fhp/Segment/Common/Kik.php index 8b8f1773..2caa6405 100644 --- a/lib/Fhp/Segment/Common/Kik.php +++ b/lib/Fhp/Segment/Common/Kik.php @@ -19,7 +19,6 @@ class Kik extends BaseDeg /** Max length: 30 (Mandatory/absent depending on the country) */ public ?string $kreditinstitutscode = null; - /** {@inheritdoc} */ public function validate() { parent::validate(); diff --git a/lib/Fhp/Segment/Common/Kti.php b/lib/Fhp/Segment/Common/Kti.php index 9c47a71b..bd10de7b 100644 --- a/lib/Fhp/Segment/Common/Kti.php +++ b/lib/Fhp/Segment/Common/Kti.php @@ -26,7 +26,6 @@ class Kti extends BaseDeg implements AccountInfo public ?string $unterkontomerkmal = null; public ?Kik $kreditinstitutskennung = null; - /** {@inheritdoc} */ public function validate() { parent::validate(); @@ -58,13 +57,11 @@ public static function fromAccount(SEPAAccount $account): Kti return $result; } - /** {@inheritdoc} */ public function getAccountNumber(): string { return $this->iban ?? $this->kontonummer; } - /** {@inheritdoc} */ public function getBankIdentifier(): ?string { return $this->bic ?? $this->kreditinstitutskennung->kreditinstitutscode; diff --git a/lib/Fhp/Segment/Common/Kto.php b/lib/Fhp/Segment/Common/Kto.php index 1671816c..46436e38 100644 --- a/lib/Fhp/Segment/Common/Kto.php +++ b/lib/Fhp/Segment/Common/Kto.php @@ -31,13 +31,11 @@ public static function fromAccount(SEPAAccount $account): Kto return static::create($account->getAccountNumber(), Kik::create($account->getBlz())); } - /** {@inheritdoc} */ public function getAccountNumber(): string { return $this->kontonummer; } - /** {@inheritdoc} */ public function getBankIdentifier(): ?string { return $this->kik->kreditinstitutscode; diff --git a/lib/Fhp/Segment/Common/KtvV3.php b/lib/Fhp/Segment/Common/KtvV3.php index 194ad439..b6144ac4 100644 --- a/lib/Fhp/Segment/Common/KtvV3.php +++ b/lib/Fhp/Segment/Common/KtvV3.php @@ -37,13 +37,11 @@ public static function fromAccount(SEPAAccount $account): KtvV3 return static::create($account->getAccountNumber(), $account->getSubAccount(), Kik::create($account->getBlz())); } - /** {@inheritdoc} */ public function getAccountNumber(): string { return $this->kontonummer ?: ''; } - /** {@inheritdoc} */ public function getBankIdentifier(): ?string { return $this->kik->kreditinstitutscode; diff --git a/lib/Fhp/Segment/Common/Ktz.php b/lib/Fhp/Segment/Common/Ktz.php index b5ee4d80..34b9592d 100644 --- a/lib/Fhp/Segment/Common/Ktz.php +++ b/lib/Fhp/Segment/Common/Ktz.php @@ -24,13 +24,11 @@ class Ktz extends BaseDeg implements AccountInfo public ?string $unterkontomerkmal = null; public Kik $kreditinstitutskennung; - /** {@inheritdoc} */ public function getAccountNumber(): string { return $this->iban ?? $this->kontonummer; } - /** {@inheritdoc} */ public function getBankIdentifier(): ?string { return $this->bic ?? $this->kreditinstitutskennung->kreditinstitutscode; diff --git a/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV1.php b/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV1.php index f473ae0e..8b33098c 100644 --- a/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV1.php +++ b/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV1.php @@ -22,7 +22,6 @@ class ErlaubteGeschaeftsvorfaelleV1 extends BaseDeg implements ErlaubteGeschaeft /** If present, must be greater than 0 */ public ?int $limitTage = null; - /** {@inheritdoc} */ public function getGeschaeftsvorfall(): string { return $this->geschaeftsvorfall; diff --git a/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV2.php b/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV2.php index 77660788..23b0dbad 100644 --- a/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV2.php +++ b/lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV2.php @@ -22,7 +22,6 @@ class ErlaubteGeschaeftsvorfaelleV2 extends BaseDeg implements ErlaubteGeschaeft /** Only allowed for limitart==Z, must be greater than zero. */ public ?int $limitTage = null; - /** {@inheritdoc} */ public function getGeschaeftsvorfall(): string { return $this->geschaeftsvorfall; diff --git a/lib/Fhp/Segment/HIUPD/HIUPDv4.php b/lib/Fhp/Segment/HIUPD/HIUPDv4.php index 234bc25a..4429fddc 100644 --- a/lib/Fhp/Segment/HIUPD/HIUPDv4.php +++ b/lib/Fhp/Segment/HIUPD/HIUPDv4.php @@ -29,14 +29,12 @@ class HIUPDv4 extends BaseSegment implements HIUPD /** @var ErlaubteGeschaeftsvorfaelleV1[]|null @Max(98) */ public ?array $erlaubteGeschaeftsvorfaelle = null; - /** {@inheritdoc} */ public function matchesAccount(SEPAAccount $account): bool { return !is_null($this->kontoverbindung->kontonummer) && $this->kontoverbindung->kontonummer == $account->getAccountNumber(); } - /** {@inheritdoc} */ public function getErlaubteGeschaeftsvorfaelle(): array { return $this->erlaubteGeschaeftsvorfaelle ?? []; diff --git a/lib/Fhp/Segment/HIUPD/HIUPDv6.php b/lib/Fhp/Segment/HIUPD/HIUPDv6.php index 94e5c429..710eae49 100644 --- a/lib/Fhp/Segment/HIUPD/HIUPDv6.php +++ b/lib/Fhp/Segment/HIUPD/HIUPDv6.php @@ -51,13 +51,11 @@ class HIUPDv6 extends BaseSegment implements HIUPD */ public ?string $erweiterungKontobezogen = null; - /** {@inheritdoc} */ public function matchesAccount(SEPAAccount $account): bool { return !is_null($this->iban) && $this->iban == $account->getIban(); } - /** {@inheritdoc} */ public function getErlaubteGeschaeftsvorfaelle(): array { return $this->erlaubteGeschaeftsvorfaelle ?? []; diff --git a/lib/Fhp/Segment/TAB/HITABv4.php b/lib/Fhp/Segment/TAB/HITABv4.php index 9973795c..046c1af7 100644 --- a/lib/Fhp/Segment/TAB/HITABv4.php +++ b/lib/Fhp/Segment/TAB/HITABv4.php @@ -22,7 +22,6 @@ class HITABv4 extends BaseSegment implements HITAB /** @var TanMediumListeV4[]|null @Max(99) */ public ?array $tanMediumListe = null; - /** {@inheritdoc} */ public function getTanMediumListe(): array { return $this->tanMediumListe; diff --git a/lib/Fhp/Segment/TAB/HITABv5.php b/lib/Fhp/Segment/TAB/HITABv5.php index c7f89160..6a72c8cb 100644 --- a/lib/Fhp/Segment/TAB/HITABv5.php +++ b/lib/Fhp/Segment/TAB/HITABv5.php @@ -22,7 +22,6 @@ class HITABv5 extends BaseSegment implements HITAB /** @var TanMediumListeV5[]|null @Max(99) */ public ?array $tanMediumListe = null; - /** {@inheritdoc} */ public function getTanMediumListe(): array { return $this->tanMediumListe; diff --git a/lib/Fhp/Segment/TAB/TanMediumListeV4.php b/lib/Fhp/Segment/TAB/TanMediumListeV4.php index 4f534016..abe3daef 100644 --- a/lib/Fhp/Segment/TAB/TanMediumListeV4.php +++ b/lib/Fhp/Segment/TAB/TanMediumListeV4.php @@ -56,13 +56,11 @@ class TanMediumListeV4 extends BaseDeg implements TanMediumListe /** JJJJMMTT gemäß ISO 8601 */ public ?string $freigeschaltetAm = null; - /** {@inheritdoc} */ public function getName(): string { return $this->bezeichnungDesTanMediums; } - /** {@inheritdoc} */ public function getPhoneNumber(): ?string { return $this->mobiltelefonnummer !== null ? $this->mobiltelefonnummer : $this->mobiltelefonnummerVerschleiert; diff --git a/lib/Fhp/Segment/TAB/TanMediumListeV5.php b/lib/Fhp/Segment/TAB/TanMediumListeV5.php index be5aa7f5..13bf8b0a 100644 --- a/lib/Fhp/Segment/TAB/TanMediumListeV5.php +++ b/lib/Fhp/Segment/TAB/TanMediumListeV5.php @@ -59,13 +59,11 @@ class TanMediumListeV5 extends BaseDeg implements TanMediumListe /** JJJJMMTT gemäß ISO 8601 */ public ?string $freigeschaltetAm = null; - /** {@inheritdoc} */ public function getName(): string { return $this->bezeichnungDesTanMediums; } - /** {@inheritdoc} */ public function getPhoneNumber(): ?string { return $this->mobiltelefonnummer !== null ? $this->mobiltelefonnummer : $this->mobiltelefonnummerVerschleiert; diff --git a/lib/Fhp/Segment/TAN/HITANv6.php b/lib/Fhp/Segment/TAN/HITANv6.php index 2d641f2d..c1032f99 100644 --- a/lib/Fhp/Segment/TAN/HITANv6.php +++ b/lib/Fhp/Segment/TAN/HITANv6.php @@ -55,21 +55,18 @@ class HITANv6 extends BaseSegment implements HITAN */ public ?string $bezeichnungDesTanMediums = null; - /** {@inheritdoc} */ public function getProcessId(): string { // Note: This is non-null because tanProzess==4. return $this->auftragsreferenz; } - /** {@inheritdoc} */ public function getChallenge(): ?string { // Note: This is non-null because tanProzess==4. return $this->challenge === static::DUMMY_CHALLENGE ? null : $this->challenge; } - /** {@inheritdoc} */ public function getTanMediumName(): ?string { return $this->bezeichnungDesTanMediums; diff --git a/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV6.php b/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV6.php index 66959177..38ae6cbb 100644 --- a/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV6.php +++ b/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV6.php @@ -49,115 +49,96 @@ class VerfahrensparameterZweiSchrittVerfahrenV6 extends BaseDeg implements TanMo public bool $antwortHhdUcErforderlich; public ?int $anzahlUnterstuetzterAktiverTanMedien = null; - /** {@inheritdoc} */ public function getId(): int { return $this->sicherheitsfunktion; } - /** {@inheritdoc} */ public function getName(): string { return $this->nameDesZweiSchrittVerfahrens; } - /** {@inheritdoc} */ public function isProzessvariante2(): bool { return $this->tanProzess === HKTAN::TAN_PROZESS_2; } - /** {@inheritdoc} */ public function isDecoupled(): bool { return false; } - /** {@inheritdoc} */ public function getSmsAbbuchungskontoErforderlich(): bool { return $this->smsAbbuchungskontoErforderlich === 2; } - /** {@inheritdoc} */ public function getAuftraggeberkontoErforderlich(): bool { return $this->auftraggeberkontoErforderlich === 2; } - /** {@inheritdoc} */ public function getChallengeKlasseErforderlich(): bool { return $this->challengeKlasseErforderlich; } - /** {@inheritdoc} */ public function getAntwortHhdUcErforderlich(): bool { return $this->antwortHhdUcErforderlich; } - /** {@inheritdoc} */ public function getChallengeLabel(): string { return $this->textZurBelegungDesRueckgabewertes; } - /** {@inheritdoc} */ public function getMaxChallengeLength(): int { return $this->maximaleLaengeDesRueckgabewertes; } - /** {@inheritdoc} */ public function getMaxTanLength(): int { return $this->maximaleLaengeDesTanEingabewertes; } - /** {@inheritdoc} */ public function getTanFormat(): int { return $this->erlaubtesFormat; } - /** {@inheritdoc} */ public function needsTanMedium(): bool { return $this->bezeichnungDesTanMediumsErforderlich === 2 && $this->anzahlUnterstuetzterAktiverTanMedien > 0; } - /** {@inheritdoc} */ public function getMaxDecoupledChecks(): int { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function getFirstDecoupledCheckDelaySeconds(): int { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function getPeriodicDecoupledCheckDelaySeconds(): int { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function allowsManualConfirmation(): bool { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function allowsAutomatedPolling(): bool { throw new \RuntimeException('Only allowed for decoupled TAN modes'); } - /** {@inheritdoc} */ public function createHKTAN(): HKTAN { return HKTANv6::createEmpty(); diff --git a/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV7.php b/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV7.php index 17c47c6c..3834f805 100644 --- a/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV7.php +++ b/lib/Fhp/Segment/TAN/VerfahrensparameterZweiSchrittVerfahrenV7.php @@ -70,67 +70,56 @@ class VerfahrensparameterZweiSchrittVerfahrenV7 extends BaseDeg implements TanMo /** Maybe present if isDecoupled. */ public ?bool $automatisierteStatusabfragenErlaubt = null; - /** {@inheritdoc} */ public function getId(): int { return $this->sicherheitsfunktion; } - /** {@inheritdoc} */ public function getName(): string { return $this->nameDesZweiSchrittVerfahrens; } - /** {@inheritdoc} */ public function isProzessvariante2(): bool { return $this->tanProzess === HKTAN::TAN_PROZESS_2; } - /** {@inheritdoc} */ public function isDecoupled(): bool { return $this->dkTanVerfahren === 'Decoupled' || $this->dkTanVerfahren === 'DecoupledPush'; } - /** {@inheritdoc} */ public function getSmsAbbuchungskontoErforderlich(): bool { return $this->smsAbbuchungskontoErforderlich === 2; } - /** {@inheritdoc} */ public function getAuftraggeberkontoErforderlich(): bool { return $this->auftraggeberkontoErforderlich === 2; } - /** {@inheritdoc} */ public function getChallengeKlasseErforderlich(): bool { return $this->challengeKlasseErforderlich; } - /** {@inheritdoc} */ public function getAntwortHhdUcErforderlich(): bool { return $this->antwortHhdUcErforderlich; } - /** {@inheritdoc} */ public function getChallengeLabel(): string { return $this->textZurBelegungDesRueckgabewertes; } - /** {@inheritdoc} */ public function getMaxChallengeLength(): int { return $this->maximaleLaengeDesRueckgabewertes; } - /** {@inheritdoc} */ public function getMaxTanLength(): int { if ($this->isDecoupled()) { @@ -142,7 +131,6 @@ public function getMaxTanLength(): int return $this->maximaleLaengeDesTanEingabewertes; } - /** {@inheritdoc} */ public function getTanFormat(): int { if ($this->isDecoupled()) { @@ -154,13 +142,11 @@ public function getTanFormat(): int return $this->erlaubtesFormat; } - /** {@inheritdoc} */ public function needsTanMedium(): bool { return $this->bezeichnungDesTanMediumsErforderlich === 2 && $this->anzahlUnterstuetzterAktiverTanMedien > 0; } - /** {@inheritdoc} */ public function getMaxDecoupledChecks(): int { if (!$this->isDecoupled()) { @@ -172,7 +158,6 @@ public function getMaxDecoupledChecks(): int return $this->maximaleAnzahlStatusabfragen; } - /** {@inheritdoc} */ public function getFirstDecoupledCheckDelaySeconds(): int { if (!$this->isDecoupled()) { @@ -184,7 +169,6 @@ public function getFirstDecoupledCheckDelaySeconds(): int return $this->wartezeitVorErsterStatusabfrage; } - /** {@inheritdoc} */ public function getPeriodicDecoupledCheckDelaySeconds(): int { if (!$this->isDecoupled()) { @@ -196,7 +180,6 @@ public function getPeriodicDecoupledCheckDelaySeconds(): int return $this->wartezeitVorNaechsterStatusabfrage; } - /** {@inheritdoc} */ public function allowsManualConfirmation(): bool { if (!$this->isDecoupled()) { @@ -208,7 +191,6 @@ public function allowsManualConfirmation(): bool return $this->manuelleBestaetigungMoeglich; } - /** {@inheritdoc} */ public function allowsAutomatedPolling(): bool { if (!$this->isDecoupled()) { @@ -220,7 +202,6 @@ public function allowsAutomatedPolling(): bool return $this->automatisierteStatusabfragenErlaubt; } - /** {@inheritdoc} */ public function createHKTAN(): HKTAN { return HKTANv7::createEmpty(); diff --git a/lib/Tests/Fhp/FinTsPeer.php b/lib/Tests/Fhp/FinTsPeer.php index 3a6e2767..5af27db9 100644 --- a/lib/Tests/Fhp/FinTsPeer.php +++ b/lib/Tests/Fhp/FinTsPeer.php @@ -23,14 +23,12 @@ public function __construct(FinTsOptions $options, ?Credentials $credentials) parent::__construct($options, $credentials); } - /** {@inheritdoc} */ protected function newConnection(): Connection { return self::$mockConnection; } /** - * {@inheritdoc} * @throws ServerException */ public function endDialog(bool $isAnonymous = false) // parent::endDialog() is protected From a6929b6ce7ba347479dc74338218ecb8419123f6 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Mon, 13 Oct 2025 21:44:10 +0200 Subject: [PATCH 19/19] Fix CS Fixer warnings on its own config file --- .php-cs-fixer.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 9eb8056a..9171a9a2 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -12,19 +12,19 @@ // But then we have some exclusions, i.e. we disable some of the checks/rules from Symfony: // Logic - 'yoda_style' => FALSE, // Allow both Yoda-style and regular comparisons. + 'yoda_style' => false, // Allow both Yoda-style and regular comparisons. // Whitespace - 'blank_line_before_statement' => FALSE, // Don't put blank lines before `return` statements. - 'concat_space' => FALSE, // Allow spaces around string concatenation operator. - 'blank_line_after_opening_tag' => FALSE, // Allow file-level @noinspection suppressions to live on the ` FALSE, // Allow `throw` statements to span multiple lines. + 'blank_line_before_statement' => false, // Don't put blank lines before `return` statements. + 'concat_space' => false, // Allow spaces around string concatenation operator. + 'blank_line_after_opening_tag' => false, // Allow file-level @noinspection suppressions to live on the ` false, // Allow `throw` statements to span multiple lines. // phpDoc - 'phpdoc_align' => FALSE, // Don't add spaces within phpDoc just to make parameter names / descriptions align. - 'phpdoc_annotation_without_dot' => FALSE, // Allow terminating dot on @param and such. - 'phpdoc_no_alias_tag' => FALSE, // Allow @link in addition to @see. - 'phpdoc_separation' => FALSE, // Don't put blank line between @params, @throws and @return. - 'phpdoc_summary' => FALSE, // Don't force terminating dot on the first line. + 'phpdoc_align' => false, // Don't add spaces within phpDoc just to make parameter names / descriptions align. + 'phpdoc_annotation_without_dot' => false, // Allow terminating dot on @param and such. + 'phpdoc_no_alias_tag' => false, // Allow @link in addition to @see. + 'phpdoc_separation' => false, // Don't put blank line between @params, @throws and @return. + 'phpdoc_summary' => false, // Don't force terminating dot on the first line. ]) ->setFinder($finder);