From 201cd2fc2153aef45d9ac3626dc3637512c00c69 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sat, 18 Oct 2025 22:38:13 +0200 Subject: [PATCH] #477 Add example code for Verification of Payee --- Samples/directDebit_Sephpa.php | 6 +- Samples/directDebit_phpSepaXml.php | 6 +- Samples/transfer.php | 6 +- Samples/vop.php | 138 +++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 Samples/vop.php diff --git a/Samples/directDebit_Sephpa.php b/Samples/directDebit_Sephpa.php index 5113dafc..171d0fcf 100644 --- a/Samples/directDebit_Sephpa.php +++ b/Samples/directDebit_Sephpa.php @@ -46,9 +46,9 @@ $sendSEPADirectDebit = \Fhp\Action\SendSEPADirectDebit::create($oneAccount, $xml); $fints->execute($sendSEPADirectDebit); -if ($sendSEPADirectDebit->needsTan()) { - handleStrongAuthentication($sendSEPADirectDebit); // See login.php for the implementation. -} + +require_once 'vop.php'; +handleVopAndAuthentication($sendSEPADirectDebit); // Debit requests don't produce any result we could receive through a getter, but we still need to make sure it's done. $sendSEPADirectDebit->ensureDone(); diff --git a/Samples/directDebit_phpSepaXml.php b/Samples/directDebit_phpSepaXml.php index ac6c1e49..aaa41bc1 100644 --- a/Samples/directDebit_phpSepaXml.php +++ b/Samples/directDebit_phpSepaXml.php @@ -62,9 +62,9 @@ $sendSEPADirectDebit = \Fhp\Action\SendSEPADirectDebit::create($oneAccount, $sepaDD->toXML('pain.008.001.02')); $fints->execute($sendSEPADirectDebit); -if ($sendSEPADirectDebit->needsTan()) { - handleStrongAuthentication($sendSEPADirectDebit); // See login.php for the implementation. -} + +require_once 'vop.php'; +handleVopAndAuthentication($sendSEPADirectDebit); // Debit requests don't produce any result we could receive through a getter, but we still need to make sure it's done. $sendSEPADirectDebit->ensureDone(); diff --git a/Samples/transfer.php b/Samples/transfer.php index 99d5d5af..6aae1816 100644 --- a/Samples/transfer.php +++ b/Samples/transfer.php @@ -58,9 +58,9 @@ $sendSEPATransfer = \Fhp\Action\SendSEPATransfer::create($oneAccount, $sepaDD->toXML()); $fints->execute($sendSEPATransfer); -if ($sendSEPATransfer->needsTan()) { - handleStrongAuthentication($sendSEPATransfer); // See login.php for the implementation. -} + +require_once 'vop.php'; +handleVopAndAuthentication($sendSEPATransfer); // SEPA transfers don't produce any result we could receive through a getter, but we still need to make sure it's done. $sendSEPATransfer->ensureDone(); diff --git a/Samples/vop.php b/Samples/vop.php new file mode 100644 index 00000000..2864e5c7 --- /dev/null +++ b/Samples/vop.php @@ -0,0 +1,138 @@ +isDone()) { + if ($action->needsTan()) { + handleStrongAuthentication($action); // See login.php for the implementation. + } elseif ($action->needsPollingWait()) { + handlePollingWait($action); + } elseif ($action->needsVopConfirmation()) { + handleVopConfirmation($action); + } else { + throw new \AssertionError( + 'Action is not done but also does not need anything to be done. Did you execute() it?' + ); + } + } +} + +/** + * Waits for the amount of time that the bank prescribed and then polls the server for a status update. + * @param \Fhp\BaseAction $action An action for which {@link \Fhp\BaseAction::needsPollingWait()} returns true. + * @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details. + */ +function handlePollingWait(\Fhp\BaseAction $action): void +{ + global $fints, $options, $credentials; // From login.php + + // Tell the user what the bank had to say (if anything). + $pollingInfo = $action->getPollingInfo(); + if ($infoText = $pollingInfo->getInformationForUser()) { + echo $infoText . PHP_EOL; + } + + // Optional: If the wait is too long for your PHP process to remain alive (i.e. your server would kill the process), + // you can persist the state as shown here and instead send a response to the client-side application indicating + // that the operation is still ongoing. Then after an appropriate amount of time, the client can send another + // request, spawning a new PHP process, where you can restore the state as shown below. + if ($optionallyPersistEverything = false) { + $persistedAction = serialize($action); + $persistedFints = $fints->persist(); + + // These are two strings (watch out, they are NOT necessarily UTF-8 encoded), which you can store anywhere. + // This example code stores them in a text file, but you might write them to your database (use a BLOB, not a + // CHAR/TEXT field to allow for arbitrary encoding) or in some other storage (possibly base64-encoded to make it + // ASCII). + file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction])); + } + + // Wait for (at least) the prescribed amount of time. -------------------------------------------------------------- + // Note: In your real application, you may be doing this waiting on the client and then send a fresh request to your + // server. + $waitSecs = $pollingInfo->getNextAttemptInSeconds() ?: 5; + echo "Waiting for $waitSecs seconds before polling the bank server again..." . PHP_EOL; + sleep($waitSecs); + + // 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); + } + + $fints->pollAction($action); + // Now the action is in a new state, which the caller of this function (handleVopAndAuthentication) will deal with. +} + +/** + * Asks the user to confirm + * @param \Fhp\BaseAction $action An action for which {@link \Fhp\BaseAction::needsVopConfirmation()} returns true. + * @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details. + */ +function handleVopConfirmation(\Fhp\BaseAction $action): void +{ + global $fints, $options, $credentials; // From login.php + + $vopConfirmationRequest = $action->getVopConfirmationRequest(); + if ($infoText = $vopConfirmationRequest->getInformationForUser()) { + echo $infoText . PHP_EOL; + } + echo match ($vopConfirmationRequest->getVerificationResult()) { + \Fhp\Model\VopVerificationResult::CompletedFullMatch => + 'The bank says the payee information matched perfectly, but still wants you to confirm.', + \Fhp\Model\VopVerificationResult::CompletedCloseMatch => + 'The bank says the payee information does not match exactly, so please confirm.', + \Fhp\Model\VopVerificationResult::CompletedPartialMatch => + 'The bank says the payee information does not match for all transfers, so please confirm.', + \Fhp\Model\VopVerificationResult::CompletedNoMatch => + 'The bank says the payee information does not match, but you can still confirm the transfer if you want.', + \Fhp\Model\VopVerificationResult::NotApplicable => + $vopConfirmationRequest->getVerificationNotApplicableReason() == null + ? 'The bank did not provide any information about payee verification, but you can still confirm.' + : 'The bank says: ' . $vopConfirmationRequest->getVerificationNotApplicableReason(), + default => 'The bank failed to provide information about payee verification, but you can still confirm.', + } . PHP_EOL; + + // Just like in handleTan(), handleDecoupledSubmission() or handlePollingWait(), we have the option to interrupt the + // PHP process at this point, so that we can ask the user in a client application for their confirmation. + if ($optionallyPersistEverything = false) { + $persistedAction = serialize($action); + $persistedFints = $fints->persist(); + // See handlePollingWait() for how to deal with this in practice. + file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction])); + } + + echo "In light of the information provided above, do you want to confirm the execution of the transfer?" . PHP_EOL; + // Note: We currently have no way canceling the transfer; the only thing we can do is never to confirm it. + echo "If so, please type 'confirm' and hit Return. Otherwise, please kill this PHP process." . PHP_EOL; + while (trim(fgets(STDIN)) !== 'confirm') { + echo "Try again." . PHP_EOL; + } + echo "Confirming the transfer." . PHP_EOL; + $fints->confirmVop($action); + echo "Confirmed" . PHP_EOL; + // Now the action is in a new state, which the caller of this function (handleVopAndAuthentication) will deal with. +}