Skip to content

Commit 1eceec9

Browse files
authored
Verify v2 (#389)
* failing client boot skeleton * Scaffolding * SMS sending and validation * Complete happy path tests for requests * Webhook testing, error handling * Get factory to generate verify 2 client * Fix CRLF discrepancies over environments * Let's try CRLF again * Docs and constant use for readability * Webhooks reimagined, docs
1 parent 3c86ebb commit 1eceec9

36 files changed

+1901
-28
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
# Ignore all differences in line endings for requests
1414
/test/SMS/requests/* text eol=crlf
1515
/test/Voice/requests/* text eol=crlf
16+
/test/Verify2/Fixtures/Requests/* text eol=crlf

README.md

+109-19
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ $viberImage = new Vonage\Messages\Channel\Viber\ViberImage(
228228
$client->messages()->send($viberImage);
229229
```
230230

231+
## Verify Examples (v1)
232+
231233
### Starting a Verification
232234

233235
Vonage's [Verify API][doc_verify] makes it easy to prove that a user has provided their own phone number during signup,
@@ -295,6 +297,93 @@ echo "Started verification with an id of: " . $response['request_id'];
295297

296298
Once the user inputs the pin code they received, call the `/check` endpoint with the request ID and the pin to confirm the pin is correct.
297299

300+
## Verify Examples (v2)
301+
302+
### Starting a Verification
303+
304+
Vonage's Verify v2 relies more on asynchronous workflows via. webhooks, and more customisable Verification
305+
workflows to the developer. To start a verification, you'll need the API client, which is under the namespace
306+
`verify2`.
307+
308+
Making a Verify request needs a 'base' channel of communication to deliver the mode of verification. You can
309+
customise these interactions by adding different 'workflows'. For each type of workflow, there is a Verify2 class
310+
you can create that will handle the initial workflow for you. For example:
311+
312+
```php
313+
$client = new Vonage\Client(
314+
new Vonage\Client\Credentials\Basic(API_KEY, API_SECRET),
315+
);
316+
317+
$smsRequest = new \Vonage\Verify2\Request\SMSRequest('TO_NUMBER');
318+
$client->verify2()->startVerification($smsRequest);
319+
```
320+
321+
The `SMSRequest` object will resolve defaults for you, and will create a default `workflow` object to use SMS.
322+
You can, however, add multiple workflows that operate with fall-back logic. For example, if you wanted to create
323+
a Verification that tries to get a PIN code off the user via. SMS, but in case there is a problem with SMS delivery
324+
you wish to add a Voice fallback: you can add it.
325+
326+
```php
327+
$client = new Vonage\Client(
328+
new Vonage\Client\Credentials\Basic(API_KEY, API_SECRET),
329+
);
330+
331+
$smsRequest = new \Vonage\Verify2\Request\SMSRequest('TO_NUMBER', 'my-verification');
332+
$voiceWorkflow = new \Vonage\Verify2\VerifyObjects\VerificationWorkflow(\Vonage\Verify2\VerifyObjects\VerificationWorkflow::WORKFLOW_VOICE, 'TO_NUMBER');
333+
$smsRequest->addWorkflow($voiceWorkflow);
334+
$client->verify2()->startVerification($smsRequest);
335+
```
336+
This adds the voice workflow to the original SMS request. The verification request will try and resolve the process in
337+
the order that it is given (starting with the default for the type of request).
338+
339+
The base request types are as follows:
340+
341+
* `SMSRequest`
342+
* `WhatsAppRequest`
343+
* `WhatsAppInterativeRequest`
344+
* `EmailRequest`
345+
* `VoiceRequest`
346+
* `SilentAuthRequest`
347+
348+
For adding workflows, you can see the available valid workflows as constants within the `VerificationWorkflow` object.
349+
For a better developer experience, you can't create an invalid workflow due to the validation that happens on the object.
350+
351+
### Check a submitted code
352+
353+
To submit a code, you'll need to surround the method in a try/catch due to the nature of the API. If the code is correct,
354+
the method will return a `true` boolean. If it fails, it will throw the relevant Exception from the API that will need to
355+
be caught.
356+
357+
```php
358+
$code = '1234';
359+
try {
360+
$client->verify2()->check($code);
361+
} catch (\Exception $e) {
362+
var_dump($e->getMessage())
363+
}
364+
```
365+
366+
### Webhooks
367+
368+
As events happen during a verification workflow, events and updates will fired as webhooks. Incoming server requests that conform to
369+
PSR-7 standards can be hydrated into a webhook value object for nicer interactions. You can also hydrate
370+
them from a raw array. If successful, you will receive a value object back for the type of event/update. Possible webhooks are:
371+
372+
* `VerifyEvent`
373+
* `VerifyStatusUpdate`
374+
* `VerifySilentAuthUpdate`
375+
376+
```php
377+
// From a request object
378+
$verificationEvent = \Vonage\Verify2\Webhook\Factory::createFromRequest($request);
379+
var_dump($verificationEvent->getStatus());
380+
// From an array
381+
$payload = $request->getBody()->getContents()
382+
$verificationEvent = \Vonage\Verify2\Webhook\Factory::createFromArray($payload);
383+
var_dump($verificationEvent->getStatus());
384+
```
385+
386+
298387
### Making a Call
299388

300389
All `$client->voice()` methods require the client to be constructed with a `Vonage\Client\Credentials\Keypair`, or a
@@ -797,25 +886,26 @@ Check out the [documentation](https://developer.nexmo.com/number-insight/code-sn
797886

798887
## Supported APIs
799888

800-
| API | API Release Status | Supported?
801-
|----------|:---------:|:-------------:|
802-
| Account API | General Availability ||
803-
| Alerts API | General Availability ||
804-
| Application API | General Availability ||
805-
| Audit API | Beta ||
806-
| Conversation API | Beta ||
807-
| Dispatch API | Beta ||
808-
| External Accounts API | Beta ||
809-
| Media API | Beta ||
810-
| Messages API | General Availability ||
811-
| Number Insight API | General Availability ||
812-
| Number Management API | General Availability ||
813-
| Pricing API | General Availability ||
814-
| Redact API | General Availability ||
815-
| Reports API | Beta ||
816-
| SMS API | General Availability ||
817-
| Verify API | General Availability ||
818-
| Voice API | General Availability ||
889+
| API | API Release Status | Supported?
890+
|------------------------|:--------------------:|:-------------:|
891+
| Account API | General Availability ||
892+
| Alerts API | General Availability ||
893+
| Application API | General Availability ||
894+
| Audit API | Beta ||
895+
| Conversation API | Beta ||
896+
| Dispatch API | Beta ||
897+
| External Accounts API | Beta ||
898+
| Media API | Beta ||
899+
| Messages API | General Availability ||
900+
| Number Insight API | General Availability ||
901+
| Number Management API | General Availability ||
902+
| Pricing API | General Availability ||
903+
| Redact API | General Availability ||
904+
| Reports API | Beta ||
905+
| SMS API | General Availability ||
906+
| Verify API | General Availability ||
907+
| Verify API (Version 2) | Beta ||
908+
| Voice API | General Availability ||
819909

820910
## Troubleshooting
821911

phpunit.xml.dist

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
<testsuite name="default">
2121
<directory>test</directory>
2222
</testsuite>
23+
<testsuite name="verify2">
24+
<directory>test/Verify2</directory>
25+
</testsuite>
2326
</testsuites>
2427
<php>
2528
<ini name='error_reporting' value='E_ALL' />

src/Client.php

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
use Vonage\SMS\ClientFactory as SMSClientFactory;
5353
use Vonage\Messages\ClientFactory as MessagesClientFactory;
5454
use Vonage\Verify\ClientFactory as VerifyClientFactory;
55+
use Vonage\Verify2\ClientFactory as Verify2ClientFactory;
5556
use Vonage\Verify\Verification;
5657
use Vonage\Voice\ClientFactory as VoiceClientFactory;
5758
use Vonage\Logger\{LoggerAwareInterface, LoggerTrait};
@@ -81,6 +82,7 @@
8182
* @method Secrets\Client secrets()
8283
* @method SMS\Client sms()
8384
* @method Verify\Client verify()
85+
* @method Verify2\Client verify2()
8486
* @method Voice\Client voice()
8587
*
8688
* @property string restUrl
@@ -217,6 +219,7 @@ public function __construct(
217219
'secrets' => SecretsClientFactory::class,
218220
'sms' => SMSClientFactory::class,
219221
'verify' => VerifyClientFactory::class,
222+
'verify2' => Verify2ClientFactory::class,
220223
'voice' => VoiceClientFactory::class,
221224

222225
// Additional utility classes

src/Verify2/Client.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Vonage\Verify2;
4+
5+
use Laminas\Diactoros\Request;
6+
use Vonage\Client\APIClient;
7+
use Vonage\Client\APIResource;
8+
use Vonage\Client\Exception\Exception;
9+
use Vonage\Verify2\Request\BaseVerifyRequest;
10+
11+
class Client implements APIClient
12+
{
13+
public function __construct(protected APIResource $api)
14+
{
15+
}
16+
17+
public function getAPIResource(): APIResource
18+
{
19+
return $this->api;
20+
}
21+
22+
public function startVerification(BaseVerifyRequest $request): ?array
23+
{
24+
return $this->getAPIResource()->create($request->toArray());
25+
}
26+
27+
public function check(string $requestId, $code): bool
28+
{
29+
try {
30+
$response = $this->getAPIResource()->create(['code' => $code], $requestId);
31+
} catch (Exception $e) {
32+
// For horrible reasons in the API Error Handler, throw the error unless it's a 409.
33+
if ($e->getCode() === 409) {
34+
throw new \Vonage\Client\Exception\Request('Conflict: The current Verify workflow step does not support a code.');
35+
}
36+
37+
throw $e;
38+
}
39+
40+
return true;
41+
}
42+
}

src/Verify2/ClientFactory.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Vonage\Verify2;
4+
5+
use Vonage\Client\APIResource;
6+
use Vonage\Client\Credentials\Handler\BasicHandler;
7+
use Vonage\Client\Credentials\Handler\KeypairHandler;
8+
9+
class ClientFactory
10+
{
11+
public function __invoke(): Client
12+
{
13+
$api = $container->make(APIResource::class);
14+
$api->setIsHAL(false)
15+
->setErrorsOn200(false)
16+
->setClient($this->vonageClient->reveal())
17+
->setAuthHandler([new BasicHandler(), new KeypairHandler()])
18+
->setBaseUrl('https://api.nexmo.com/v2/verify/');
19+
20+
return new Client($api);
21+
}
22+
}
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
namespace Vonage\Verify2\Request;
4+
5+
use Vonage\Verify2\VerifyObjects\VerificationLocale;
6+
use Vonage\Verify2\VerifyObjects\VerificationWorkflow;
7+
8+
abstract class BaseVerifyRequest implements RequestInterface
9+
{
10+
private const TIMEOUT_MIN = 60;
11+
private const TIMEOUT_MAX = 900;
12+
private const LENGTH_MIN = 4;
13+
private const LENGTH_MAX = 10;
14+
15+
protected ?VerificationLocale $locale = null;
16+
17+
protected int $timeout = 300;
18+
19+
protected ?string $clientRef = null;
20+
21+
protected int $length = 4;
22+
23+
protected string $brand;
24+
25+
protected array $workflows = [];
26+
27+
public function getLocale(): ?VerificationLocale
28+
{
29+
return $this->locale;
30+
}
31+
32+
public function setLocale(?VerificationLocale $verificationLocale): static
33+
{
34+
$this->locale = $verificationLocale;
35+
}
36+
37+
public function getTimeout(): int
38+
{
39+
return $this->timeout;
40+
}
41+
42+
public function setTimeout(int $timeout): static
43+
{
44+
$range = [
45+
'options' => [
46+
'min_range' => self::TIMEOUT_MIN,
47+
'max_range' => self::TIMEOUT_MAX
48+
]
49+
];
50+
51+
if (!filter_var($timeout, FILTER_VALIDATE_INT, $range)) {
52+
throw new \OutOfBoundsException('Timeout ' . $timeout . ' is not valid');
53+
}
54+
55+
$this->timeout = $timeout;
56+
57+
return $this;
58+
}
59+
60+
public function getClientRef(): ?string
61+
{
62+
return $this->clientRef;
63+
}
64+
65+
public function setClientRef(?string $clientRef): static
66+
{
67+
$this->clientRef = $clientRef;
68+
69+
return $this;
70+
}
71+
72+
public function getLength(): int
73+
{
74+
return $this->length;
75+
}
76+
77+
public function setLength(int $length): static
78+
{
79+
$range = [
80+
'options' => [
81+
'min_range' => self::LENGTH_MIN,
82+
'max_range' => self::LENGTH_MAX
83+
]
84+
];
85+
86+
if (!filter_var($length, FILTER_VALIDATE_INT, $range)) {
87+
throw new \OutOfBoundsException('PIN Length ' . $length . ' is not valid');
88+
}
89+
90+
$this->length = $length;
91+
92+
return $this;
93+
}
94+
95+
public function getBrand(): string
96+
{
97+
return $this->brand;
98+
}
99+
100+
public function setBrand(string $brand): static
101+
{
102+
$this->brand = $brand;
103+
104+
return $this;
105+
}
106+
107+
public function getWorkflows(): array
108+
{
109+
return array_map(static function($workflow) {
110+
return $workflow->toArray();
111+
}, $this->workflows);
112+
}
113+
114+
public function addWorkflow(VerificationWorkflow $verificationWorkflow): static
115+
{
116+
$this->workflows[] = $verificationWorkflow;
117+
118+
return $this;
119+
}
120+
121+
public function getBaseVerifyUniversalOutputArray(): array
122+
{
123+
$returnArray = [
124+
'locale' => $this->getLocale()->getCode(),
125+
'channel_timeout' => $this->getTimeout(),
126+
'code_length' => $this->getLength(),
127+
'brand' => $this->getBrand(),
128+
'workflow' => $this->getWorkflows()
129+
];
130+
131+
if ($this->getClientRef()) {
132+
$returnArray['client_ref'] = $this->getClientRef();
133+
}
134+
135+
return $returnArray;
136+
}
137+
}

0 commit comments

Comments
 (0)