Skip to content

Commit 71df0a0

Browse files
authored
Merge pull request #517 from Philipp91/vop-samples
#477 Add VoP handling into sample code
2 parents dbcd76f + 201cd2f commit 71df0a0

File tree

4 files changed

+147
-9
lines changed

4 files changed

+147
-9
lines changed

Samples/directDebit_Sephpa.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@
4646

4747
$sendSEPADirectDebit = \Fhp\Action\SendSEPADirectDebit::create($oneAccount, $xml);
4848
$fints->execute($sendSEPADirectDebit);
49-
if ($sendSEPADirectDebit->needsTan()) {
50-
handleStrongAuthentication($sendSEPADirectDebit); // See login.php for the implementation.
51-
}
49+
50+
require_once 'vop.php';
51+
handleVopAndAuthentication($sendSEPADirectDebit);
5252

5353
// Debit requests don't produce any result we could receive through a getter, but we still need to make sure it's done.
5454
$sendSEPADirectDebit->ensureDone();

Samples/directDebit_phpSepaXml.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@
6262

6363
$sendSEPADirectDebit = \Fhp\Action\SendSEPADirectDebit::create($oneAccount, $sepaDD->toXML('pain.008.001.02'));
6464
$fints->execute($sendSEPADirectDebit);
65-
if ($sendSEPADirectDebit->needsTan()) {
66-
handleStrongAuthentication($sendSEPADirectDebit); // See login.php for the implementation.
67-
}
65+
66+
require_once 'vop.php';
67+
handleVopAndAuthentication($sendSEPADirectDebit);
6868

6969
// Debit requests don't produce any result we could receive through a getter, but we still need to make sure it's done.
7070
$sendSEPADirectDebit->ensureDone();

Samples/transfer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858

5959
$sendSEPATransfer = \Fhp\Action\SendSEPATransfer::create($oneAccount, $sepaDD->toXML());
6060
$fints->execute($sendSEPATransfer);
61-
if ($sendSEPATransfer->needsTan()) {
62-
handleStrongAuthentication($sendSEPATransfer); // See login.php for the implementation.
63-
}
61+
62+
require_once 'vop.php';
63+
handleVopAndAuthentication($sendSEPATransfer);
6464

6565
// SEPA transfers don't produce any result we could receive through a getter, but we still need to make sure it's done.
6666
$sendSEPATransfer->ensureDone();

Samples/vop.php

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
use Fhp\CurlException;
4+
use Fhp\Protocol\ServerException;
5+
use Fhp\Protocol\UnexpectedResponseException;
6+
7+
/**
8+
* SAMPLE - Helper functions for Verification of Payee. To be used together with init.php.
9+
*/
10+
11+
/** @var \Fhp\FinTs $fints */
12+
$fints = require_once 'init.php';
13+
14+
/**
15+
* To be called after the $action was already executed, this function takes care of asking the user for a TAN and VOP
16+
* confirmation, if necessary.
17+
* @param \Fhp\BaseAction $action The action, which must already have been run through {@link \Fhp\FinTs::execute()}.
18+
* @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details.
19+
*/
20+
function handleVopAndAuthentication(\Fhp\BaseAction $action): void
21+
{
22+
// NOTE: This is implemented as a `while` loop here, because this sample script runs entirely in one PHP process.
23+
// If you want to make real use of the serializations demonstrated below, in order to resume processing in a new
24+
// PHP process later (once the user has responded via your browser/client-side application), then you won't have a
25+
// loop like this, but instead you'll just run the code within each time you get a new request from the user.
26+
while (!$action->isDone()) {
27+
if ($action->needsTan()) {
28+
handleStrongAuthentication($action); // See login.php for the implementation.
29+
} elseif ($action->needsPollingWait()) {
30+
handlePollingWait($action);
31+
} elseif ($action->needsVopConfirmation()) {
32+
handleVopConfirmation($action);
33+
} else {
34+
throw new \AssertionError(
35+
'Action is not done but also does not need anything to be done. Did you execute() it?'
36+
);
37+
}
38+
}
39+
}
40+
41+
/**
42+
* Waits for the amount of time that the bank prescribed and then polls the server for a status update.
43+
* @param \Fhp\BaseAction $action An action for which {@link \Fhp\BaseAction::needsPollingWait()} returns true.
44+
* @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details.
45+
*/
46+
function handlePollingWait(\Fhp\BaseAction $action): void
47+
{
48+
global $fints, $options, $credentials; // From login.php
49+
50+
// Tell the user what the bank had to say (if anything).
51+
$pollingInfo = $action->getPollingInfo();
52+
if ($infoText = $pollingInfo->getInformationForUser()) {
53+
echo $infoText . PHP_EOL;
54+
}
55+
56+
// Optional: If the wait is too long for your PHP process to remain alive (i.e. your server would kill the process),
57+
// you can persist the state as shown here and instead send a response to the client-side application indicating
58+
// that the operation is still ongoing. Then after an appropriate amount of time, the client can send another
59+
// request, spawning a new PHP process, where you can restore the state as shown below.
60+
if ($optionallyPersistEverything = false) {
61+
$persistedAction = serialize($action);
62+
$persistedFints = $fints->persist();
63+
64+
// These are two strings (watch out, they are NOT necessarily UTF-8 encoded), which you can store anywhere.
65+
// This example code stores them in a text file, but you might write them to your database (use a BLOB, not a
66+
// CHAR/TEXT field to allow for arbitrary encoding) or in some other storage (possibly base64-encoded to make it
67+
// ASCII).
68+
file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction]));
69+
}
70+
71+
// Wait for (at least) the prescribed amount of time. --------------------------------------------------------------
72+
// Note: In your real application, you may be doing this waiting on the client and then send a fresh request to your
73+
// server.
74+
$waitSecs = $pollingInfo->getNextAttemptInSeconds() ?: 5;
75+
echo "Waiting for $waitSecs seconds before polling the bank server again..." . PHP_EOL;
76+
sleep($waitSecs);
77+
78+
// Optional: If the state was persisted above, we can restore it now (imagine this is a new PHP process).
79+
if ($optionallyPersistEverything) {
80+
$restoredState = file_get_contents(__DIR__ . '/state.txt');
81+
list($persistedInstance, $persistedAction) = unserialize($restoredState);
82+
$fints = \Fhp\FinTs::new($options, $credentials, $persistedInstance);
83+
$action = unserialize($persistedAction);
84+
}
85+
86+
$fints->pollAction($action);
87+
// Now the action is in a new state, which the caller of this function (handleVopAndAuthentication) will deal with.
88+
}
89+
90+
/**
91+
* Asks the user to confirm
92+
* @param \Fhp\BaseAction $action An action for which {@link \Fhp\BaseAction::needsVopConfirmation()} returns true.
93+
* @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details.
94+
*/
95+
function handleVopConfirmation(\Fhp\BaseAction $action): void
96+
{
97+
global $fints, $options, $credentials; // From login.php
98+
99+
$vopConfirmationRequest = $action->getVopConfirmationRequest();
100+
if ($infoText = $vopConfirmationRequest->getInformationForUser()) {
101+
echo $infoText . PHP_EOL;
102+
}
103+
echo match ($vopConfirmationRequest->getVerificationResult()) {
104+
\Fhp\Model\VopVerificationResult::CompletedFullMatch =>
105+
'The bank says the payee information matched perfectly, but still wants you to confirm.',
106+
\Fhp\Model\VopVerificationResult::CompletedCloseMatch =>
107+
'The bank says the payee information does not match exactly, so please confirm.',
108+
\Fhp\Model\VopVerificationResult::CompletedPartialMatch =>
109+
'The bank says the payee information does not match for all transfers, so please confirm.',
110+
\Fhp\Model\VopVerificationResult::CompletedNoMatch =>
111+
'The bank says the payee information does not match, but you can still confirm the transfer if you want.',
112+
\Fhp\Model\VopVerificationResult::NotApplicable =>
113+
$vopConfirmationRequest->getVerificationNotApplicableReason() == null
114+
? 'The bank did not provide any information about payee verification, but you can still confirm.'
115+
: 'The bank says: ' . $vopConfirmationRequest->getVerificationNotApplicableReason(),
116+
default => 'The bank failed to provide information about payee verification, but you can still confirm.',
117+
} . PHP_EOL;
118+
119+
// Just like in handleTan(), handleDecoupledSubmission() or handlePollingWait(), we have the option to interrupt the
120+
// PHP process at this point, so that we can ask the user in a client application for their confirmation.
121+
if ($optionallyPersistEverything = false) {
122+
$persistedAction = serialize($action);
123+
$persistedFints = $fints->persist();
124+
// See handlePollingWait() for how to deal with this in practice.
125+
file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction]));
126+
}
127+
128+
echo "In light of the information provided above, do you want to confirm the execution of the transfer?" . PHP_EOL;
129+
// Note: We currently have no way canceling the transfer; the only thing we can do is never to confirm it.
130+
echo "If so, please type 'confirm' and hit Return. Otherwise, please kill this PHP process." . PHP_EOL;
131+
while (trim(fgets(STDIN)) !== 'confirm') {
132+
echo "Try again." . PHP_EOL;
133+
}
134+
echo "Confirming the transfer." . PHP_EOL;
135+
$fints->confirmVop($action);
136+
echo "Confirmed" . PHP_EOL;
137+
// Now the action is in a new state, which the caller of this function (handleVopAndAuthentication) will deal with.
138+
}

0 commit comments

Comments
 (0)