diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 8ef9f42d..ac6b83a5 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -8,8 +8,8 @@ jobs:
# always run on push events
# only run on pull_request_target event when pull request pulls from fork repository
if: >
- github.event_name == 'push' ||
- github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
+ github.event_name == 'push' ||
+ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
strategy:
fail-fast: false
matrix:
@@ -31,18 +31,16 @@ jobs:
- run: composer test
- php7-4:
- name: Unit Tests php7.4 (php ${{ matrix.php-version }})
+ php8-2:
+ name: Unit Tests php8.2 (php ${{ matrix.php-version }})
runs-on: ubuntu-latest
- # always run on push events
- # only run on pull_request_target event when pull request pulls from fork repository
if: >
- github.event_name == 'push' ||
+ github.event_name == 'push' ||
github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
strategy:
fail-fast: false
matrix:
- php-version: [ 7.4 ]
+ php-version: [ 8.2 ]
steps:
- uses: actions/checkout@v2
@@ -51,24 +49,24 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
+ - run: composer remove --dev --no-update --no-interaction friendsofphp/php-cs-fixer
+
- run: composer self-update
- run: composer install --no-interaction --prefer-source --dev
- run: composer test
- php8-0:
- name: Unit Tests php8.0 (php ${{ matrix.php-version }})
+ php8-3:
+ name: Unit Tests php8.3 (php ${{ matrix.php-version }})
runs-on: ubuntu-latest
- # always run on push events
- # only run on pull_request_target event when pull request pulls from fork repository
if: >
- github.event_name == 'push' ||
+ github.event_name == 'push' ||
github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
strategy:
fail-fast: false
matrix:
- php-version: [ 8.0 ]
+ php-version: [ 8.3 ]
steps:
- uses: actions/checkout@v2
@@ -77,6 +75,34 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
+ - run: composer remove --dev --no-update --no-interaction friendsofphp/php-cs-fixer
+
+ - run: composer self-update
+
+ - run: composer install --no-interaction --prefer-source --dev
+
+ - run: composer test
+
+ php8-4:
+ name: Unit Tests php8.4 (php ${{ matrix.php-version }})
+ runs-on: ubuntu-latest
+ if: >
+ github.event_name == 'push' ||
+ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: [ 8.4 ]
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: shivammathur/setup-php@2.9.0
+ with:
+ php-version: ${{ matrix.php-version }}
+
+ - run: composer remove --dev --no-update --no-interaction friendsofphp/php-cs-fixer
+
- run: composer self-update
- run: composer install --no-interaction --prefer-source --dev
@@ -84,13 +110,13 @@ jobs:
- run: composer test
protobuf:
- name: Unit Tests With Protobuf C Extension 3.13 (php ${{ matrix.php-version }})
+ name: Unit Tests With Protobuf C Extension (php ${{ matrix.php-version }})
runs-on: ubuntu-latest
# always run on push events
# only run on pull_request_target event when pull request pulls from fork repository
if: >
- github.event_name == 'push' ||
- github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
+ github.event_name == 'push' ||
+ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
strategy:
fail-fast: false
matrix:
@@ -109,4 +135,3 @@ jobs:
- run: composer install --no-interaction --prefer-source --dev
- run: composer test
-
diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache
new file mode 100644
index 00000000..c0dde319
--- /dev/null
+++ b/.php-cs-fixer.cache
@@ -0,0 +1 @@
+{"php":"8.1.27","version":"3.22.0:v3.22.0#92b019f6c8d79aa26349d0db7671d37440dc0ff3","indent":" ","lineEnding":"\n","rules":{"array_syntax":{"syntax":"short"},"no_unused_imports":true,"ordered_imports":{"imports_order":["const","class","function"]},"php_unit_fqcn_annotation":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true},"hashes":{"\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder2419\/examples\/digitalidentity\/app\/Http\/Controllers\/IdentityController.php":"369515522c3efd6cd55a8363d4e97c05","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder4308\/src\/Identity\/Policy\/Policy.php":"e1bca74eaafe5271dd1a38769fe1c3b2","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder3692\/src\/Identity\/Policy\/PolicyBuilder.php":"88302b88aba33563661d4b989b5dc429","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder2054\/tests\/Identity\/Policy\/PolicyBuilderTest.php":"a262a261102744a1acf6d5d0b421dc44","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder5221\/src\/Identity\/ReceiptBuilder.php":"2e6ef33d3401f7cbd36145ad66b3b2ef","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder289\/examples\/digitalidentity\/app\/Http\/Controllers\/ReceiptController.php":"e79ec7e1511895c954f77c713d435ad2","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder2348\/tests\/Identity\/ReceiptTest.php":"b602e6828020fef411df597e17fa7c88","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder4139\/examples\/digitalidentity\/routes\/web.php":"dcdc77843f3e59dd61467a324edf0c77","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder5396\/src\/Identity\/Receipt.php":"4744c8887009fd9ffbf084f99021eb1c","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder5193\/src\/Identity\/WrappedReceipt.php":"3a77a22be093a1da75438ea2bb9fcb20","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder4416\/examples\/digitalidentity\/app\/Http\/Controllers\/AdvancedIdentityController.php":"6b5c23f2ce8da246bc41d136d87ecdb6","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder5544\/examples\/digitalidentity\/app\/Http\/Controllers\/AdvancedIdentityController.php":"6b5c23f2ce8da246bc41d136d87ecdb6","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder4533\/src\/Identity\/Policy\/Policy.php":"bea4b7ebb268fca1ad719f933ec82cbd","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder3559\/tests\/Identity\/Policy\/PolicyBuilderTest.php":"f6d7380ae2db4eca426bb39ccfb3a900","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder2922\/examples\/digitalidentity\/routes\/web.php":"fdf260e4dfd18c8ba12078943564875a","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder3947\/src\/Constants.php":"4bb1127c9665c5d0496b90ea3211951d","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder4440\/src\/Constants.php":"99a3224f6e3fcae067362798bbab64f0","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder4745\/src\/Constants.php":"afc40e02bdc3a87ff7a874826447c604","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder1311\/src\/Constants.php":"77a8a39eac3e973495b7719ebd41509e","\/private\/var\/folders\/b6\/tqq9d7y54ll62fjfysz50ry80000gn\/T\/PHP CS Fixertemp_folder1635\/examples\/digitalidentity\/app\/Http\/Controllers\/ReceiptController.php":"10f70ffe111a0030b29762494cc5de7d"}}
\ No newline at end of file
diff --git a/README.md b/README.md
index baf6ffee..4cfbbe17 100755
--- a/README.md
+++ b/README.md
@@ -27,9 +27,12 @@ Please feel free to reach out
## Requirements
-* PHP ^7.4 || ^8.0 || ^8.1
+* PHP ^8.1
* CURL PHP extension (must support TLSv1.2)
+> **Breaking change:** this SDK release supports PHP 8.1 and above only.
+> Support for PHP 7.4 and PHP 8.0 has been removed.
+> If you are still running on PHP 7.4/8.0, please remain on an earlier SDK version until you can upgrade your runtime.
### Recommended (optional)
- [Protobuf C extension](https://github.com/protocolbuffers/protobuf/tree/master/php) (PHP package will be used by default)
@@ -42,13 +45,13 @@ Add the Yoti SDK dependency:
```json
"require": {
- "yoti/yoti-php-sdk" : "^4.1"
+ "yoti/yoti-php-sdk" : "^4.5.0"
}
```
Or run this Composer command
```console
-$ composer require yoti/yoti-php-sdk "^4.1"
+$ composer require yoti/yoti-php-sdk "^4.5.0"
```
## Setup
diff --git a/composer.json b/composer.json
index 46af649a..91b4d313 100755
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "yoti/yoti-php-sdk",
"description": "Yoti SDK for quickly integrating your PHP backend with Yoti",
- "version": "4.2.2",
+ "version": "4.5.0",
"keywords": [
"yoti",
"sdk"
@@ -9,13 +9,13 @@
"homepage": "https://yoti.com",
"license": "MIT",
"require": {
- "php": "^7.4 || ^8.0 || ^8.1",
+ "php": "^8.1",
"ext-json": "*",
- "google/protobuf": "^3.10",
- "phpseclib/phpseclib": "^3.0",
+ "google/protobuf": "^4.33.6",
+ "phpseclib/phpseclib": "^3.0.50",
"guzzlehttp/guzzle": "^7.0",
"psr/http-client": "^1.0",
- "psr/http-message": "^1.0",
+ "psr/http-message": "^2.0",
"guzzlehttp/psr7": "^2.4",
"ext-openssl": "*"
},
@@ -66,4 +66,4 @@
"phpstan/extension-installer": true
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/digitalidentity/README.md b/examples/digitalidentity/README.md
index 8faa9b32..9bf2d1b3 100644
--- a/examples/digitalidentity/README.md
+++ b/examples/digitalidentity/README.md
@@ -18,7 +18,10 @@ This example requires [Docker](https://docs.docker.com/)
* Visit [https://localhost:4002](https://localhost:4002)
* Run the `docker-compose stop` command to stop the containers.
-> To see how to retrieve activity details using the one time use token, refer to the [digitalidentity controller](app/Http/Controllers/IdentityController.php)
-
+> To see how to retrieve a profile using share receipt, refer to the [receipt controller](app/Http/Controllers/ReceiptController.php)
## Digital Identity Example
* Visit [/generate-share](https://localhost:4002/generate-share)
+## Digital Identity(Advanced) Share Example
+* Visit [/generate-advanced-identity-share](https://localhost:4002/generate-advanced-identity-share)
+* ## Digital Identity DBS Example
+* Visit [/generate-dbs-share](https://localhost:4002/generate-dbs-share)
diff --git a/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php b/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php
index cdfda0f6..c084faf2 100644
--- a/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php
+++ b/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php
@@ -4,12 +4,10 @@
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Log;
-use mysql_xdevapi\Exception;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Yoti\DigitalIdentityClient;
use Yoti\Identity\Policy\PolicyBuilder;
use Yoti\Identity\ShareSessionRequestBuilder;
-use Yoti\YotiClient;
class AdvancedIdentityController extends BaseController
{
diff --git a/examples/digitalidentity/app/Http/Controllers/DbsController.php b/examples/digitalidentity/app/Http/Controllers/DbsController.php
new file mode 100644
index 00000000..e8e47e74
--- /dev/null
+++ b/examples/digitalidentity/app/Http/Controllers/DbsController.php
@@ -0,0 +1,53 @@
+withIdentityProfileRequirements((object)[
+ 'trust_framework' => 'UK_TFIDA',
+ 'scheme' => [
+ 'type' => 'DBS',
+ 'objective' => 'BASIC'
+ ]
+ ])
+ ->build();
+
+ $redirectUri = 'https://host/redirect/';
+
+ $shareSessionRequest = (new ShareSessionRequestBuilder())
+ ->withPolicy($policy)
+ ->withRedirectUri($redirectUri)
+ ->build();
+ $session = $client->createShareSession($shareSessionRequest);
+ return $session->getId();
+ }
+ catch (\Throwable $e) {
+ Log::error($e->getTraceAsString());
+ throw new BadRequestHttpException($e->getMessage());
+ }
+ }
+ public function show(DigitalIdentityClient $client)
+ {
+ try {
+ return view('dbs', [
+ 'title' => 'Digital Identity DBS Check Example',
+ 'sdkId' => $client->id
+ ]);
+ } catch (\Throwable $e) {
+ Log::error($e->getTraceAsString());
+ throw new BadRequestHttpException($e->getMessage());
+ }
+ }
+}
diff --git a/examples/digitalidentity/app/Http/Controllers/IdentityController.php b/examples/digitalidentity/app/Http/Controllers/IdentityController.php
index 72ba9fc3..ca987617 100644
--- a/examples/digitalidentity/app/Http/Controllers/IdentityController.php
+++ b/examples/digitalidentity/app/Http/Controllers/IdentityController.php
@@ -4,12 +4,10 @@
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Log;
-use mysql_xdevapi\Exception;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Yoti\DigitalIdentityClient;
use Yoti\Identity\Policy\PolicyBuilder;
use Yoti\Identity\ShareSessionRequestBuilder;
-use Yoti\YotiClient;
class IdentityController extends BaseController
{
diff --git a/examples/digitalidentity/app/Http/Controllers/ReceiptController.php b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
index 39fe75a6..40299d42 100644
--- a/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
+++ b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
@@ -3,12 +3,12 @@
namespace App\Http\Controllers;
use Yoti\DigitalIdentityClient;
-use Yoti\YotiClient;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Yoti\Profile\Attribute;
use Yoti\Profile\UserProfile;
use Yoti\Util\Logger;
+
class ReceiptController extends BaseController
{
public function show(Request $request, DigitalIdentityClient $client, ?LoggerInterface $logger = null)
@@ -17,14 +17,25 @@ public function show(Request $request, DigitalIdentityClient $client, ?LoggerInt
$logger->warning("Unknown Content Type parsing as a String");
$shareReceipt = $client->fetchShareReceipt($request->query('ReceiptID'));
-
- $profile = $shareReceipt->getProfile();
-
- return view('receipt', [
- 'fullName' => $profile->getFullName(),
- 'selfie' => $profile->getSelfie(),
- 'profileAttributes' => $this->createAttributesDisplayList($profile),
- ]);
+ if ($shareReceipt->getError() != null)
+ {
+ error_log($shareReceipt->getErrorReason()->getRequirementNotMetDetails()->getDocumentCountryIsoCode());
+ return view('receipt', [
+ 'fullName' => null,
+ 'selfie' => null,
+ 'profileAttributes' => null,
+ 'error' => $shareReceipt->getErrorReason()
+ ]);
+ }
+ else {
+ $profile = $shareReceipt->getProfile();
+ return view('receipt', [
+ 'fullName' => $profile->getFullName(),
+ 'selfie' => $profile->getSelfie(),
+ 'profileAttributes' => $this->createAttributesDisplayList($profile),
+ 'error' => null
+ ]);
+ }
}
/**
diff --git a/examples/digitalidentity/resources/views/dbs.blade.php b/examples/digitalidentity/resources/views/dbs.blade.php
new file mode 100644
index 00000000..9f325177
--- /dev/null
+++ b/examples/digitalidentity/resources/views/dbs.blade.php
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+ {{ $title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/digitalidentity/resources/views/partial/report.blade.php b/examples/digitalidentity/resources/views/partial/report.blade.php
index 28bdd3f0..ec1dc60d 100644
--- a/examples/digitalidentity/resources/views/partial/report.blade.php
+++ b/examples/digitalidentity/resources/views/partial/report.blade.php
@@ -1,39 +1,54 @@
-@foreach ($report as $key => $value)
-
-
-
-
- {{ $key }}
- |
-
-
-
- @foreach ($value as $name => $result)
- @if (is_array($result))
- @foreach ($result as $data => $view)
- @if (is_array($view))
- @foreach ($view as $key2 => $value2)
- @if (is_array($value2))
- {{json_encode($value2)}}
- @else
-
- {{ $key2 }} {{ $value2 }} |
-
- @endif
- @endforeach
- @else
-
- {{ $data }} {{ $view }} |
-
- @endif
- @endforeach
- @else
-
+@if (isset($key) && is_array($key))
+ @foreach ($report as $key => $value)
+
+
+
+
+ {{ $key }}
+ |
+
+
+
+ @foreach ($value as $name => $result)
+ @if (isset($result) && is_array($result))
+ @foreach ($result as $data => $view)
+ @if (is_array($view))
+ @foreach ($view as $key2 => $value2)
+ @if (is_array($value2))
+ {{json_encode($value2)}}
+ @else
+
+ {{ $key2 }} {{ $value2 }} |
+
+ @endif
+ @endforeach
+ @else
+
+ {{ $data }} {{ $view }} |
+
+ @endif
+ @endforeach
+ @else
+
{{ $name }} {{ $result }} |
-
- @endif
- @endforeach
+
+ @endif
+ @endforeach
-
-
+
+
@endforeach
+@else
+
+ @foreach ($report as $key => $value)
+
+
+ {{ $key }}
+
+ {!! json_encode($value, JSON_PRETTY_PRINT) !!}
+
+ |
+
+ @endforeach
+
+@endif
diff --git a/examples/digitalidentity/resources/views/receipt.blade.php b/examples/digitalidentity/resources/views/receipt.blade.php
index 1fef5463..b166dbdd 100644
--- a/examples/digitalidentity/resources/views/receipt.blade.php
+++ b/examples/digitalidentity/resources/views/receipt.blade.php
@@ -36,10 +36,37 @@
+
+ @if ($error)
+
+
+
+
+
+
+ @endif
+ @if(@$profileAttributes)
@foreach($profileAttributes as $item)
@if ($item['obj'])
@@ -96,6 +124,7 @@
@endif
@endforeach
+ @endif
diff --git a/examples/digitalidentity/routes/web.php b/examples/digitalidentity/routes/web.php
index 44933279..f0e02806 100644
--- a/examples/digitalidentity/routes/web.php
+++ b/examples/digitalidentity/routes/web.php
@@ -18,3 +18,5 @@
Route::get('/generate-session', 'IdentityController@generateSession');
Route::get('/generate-advanced-identity-share', 'AdvancedIdentityController@show');
Route::get('/generate-advanced-identity-session', 'AdvancedIdentityController@generateSession');
+Route::get('/generate-dbs-share', 'DbsController@show');
+Route::get('/generate-dbs-session', 'DbsController@generateSession');
diff --git a/examples/doc-scan/app/Console/Kernel.php b/examples/doc-scan/app/Console/Kernel.php
index 69914e99..31f4b24d 100644
--- a/examples/doc-scan/app/Console/Kernel.php
+++ b/examples/doc-scan/app/Console/Kernel.php
@@ -34,7 +34,7 @@ protected function schedule(Schedule $schedule)
*/
protected function commands()
{
- $this->load(__DIR__.'/Commands');
+ $this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
diff --git a/examples/doc-scan/app/Http/Controllers/HomeController.php b/examples/doc-scan/app/Http/Controllers/HomeController.php
index 9bd67d01..d52fdd8e 100644
--- a/examples/doc-scan/app/Http/Controllers/HomeController.php
+++ b/examples/doc-scan/app/Http/Controllers/HomeController.php
@@ -77,28 +77,41 @@ public function show(Request $request, DocScanClient $client)
->withRemoveDeceased(true)
->build();
+ //Identity Profile Requeirements Object
+ /*$identityProfileRequirements = (object)[
+ 'trust_framework' => 'UK_TFIDA',
+ 'scheme' => [
+ 'type' => 'DBS',
+ 'objective' => 'BASIC'
+ ]
+ ];*/
$sessionSpec = (new SessionSpecificationBuilder())
->withClientSessionTokenTtl(600)
- ->withResourcesTtl(90000)
+ ->withResourcesTtl(604800)
->withUserTrackingId('some-user-tracking-id')
+ //For Identity Profile Requirements Object
+ //->withBlockBiometricConsent(false) //User needs to provide consent for the liveness detection
+ //->withIdentityProfileRequirements($identityProfileRequirements)
->withRequestedCheck(
(new RequestedDocumentAuthenticityCheckBuilder())
->build()
)
->withRequestedCheck(
(new RequestedLivenessCheckBuilder())
- ->forZoomLiveness()
+ ->forStaticLiveness()
+ ->withMaxRetries(3)
->build()
)
+ /*
->withRequestedCheck(
(new RequestedWatchlistAdvancedCaCheckBuilder())
->withConfig($customConfig)
->build()
- )
+ )*/
->withRequestedCheck(
(new RequestedFaceMatchCheckBuilder())
- ->withManualCheckAlways()
+ ->withManualCheckFallback()
->build()
)
->withRequestedCheck(
@@ -116,20 +129,20 @@ public function show(Request $request, DocScanClient $client)
)
->withRequestedTask(
(new RequestedTextExtractionTaskBuilder())
- ->withManualCheckAlways()
+ ->withManualCheckFallback()
->withChipDataDesired()
->withCreateExpandedDocumentFields(true)
->build()
)
->withRequestedTask(
(new RequestedSupplementaryDocTextExtractionTaskBuilder())
- ->withManualCheckAlways()
+ ->withManualCheckFallback()
->build()
)
->withSdkConfig(
(new SdkConfigBuilder())
->withAllowsCameraAndUpload()
- ->withPrimaryColour('#2d9fff')
+ ->withPrimaryColour('#2875BC')
->withSecondaryColour('#FFFFFF')
->withFontColour('#FFFFFF')
->withLocale('en-GB')
@@ -138,6 +151,12 @@ public function show(Request $request, DocScanClient $client)
->withErrorUrl(config('app.url') . '/error')
->withPrivacyPolicyUrl(config('app.url') . '/privacy-policy')
->withBiometricConsentFlow('EARLY')
+ ->withBrandId('brand_id')
+ // Suppress specific screens to shorten the flow
+ ->withSuppressedScreens(['intro_screen', 'document_capture_instruction'])
+ // Or add screens individually:
+ // ->withSuppressedScreen('intro_screen')
+ // ->withSuppressedScreen('document_capture_instruction')
->build()
)
->withRequiredDocument(
@@ -155,14 +174,14 @@ public function show(Request $request, DocScanClient $client)
->withRequiredDocument(
(new RequiredSupplementaryDocumentBuilder())
->withObjective(
- (new ProofOfAddressObjectiveBuilder)
+ (new ProofOfAddressObjectiveBuilder())
->build()
)
->build()
)
->build();
-
+
$session = $client->createSession($sessionSpec);
$request->session()->put('YOTI_SESSION_ID', $session->getSessionId());
diff --git a/examples/doc-scan/config/cache.php b/examples/doc-scan/config/cache.php
index 4f41fdf9..a8eaf93b 100644
--- a/examples/doc-scan/config/cache.php
+++ b/examples/doc-scan/config/cache.php
@@ -99,6 +99,6 @@
|
*/
- 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
+ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
];
diff --git a/examples/doc-scan/config/database.php b/examples/doc-scan/config/database.php
index b42d9b30..3bfc47a5 100644
--- a/examples/doc-scan/config/database.php
+++ b/examples/doc-scan/config/database.php
@@ -123,7 +123,7 @@
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
- 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
+ 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [
diff --git a/examples/doc-scan/config/filesystems.php b/examples/doc-scan/config/filesystems.php
index cd9f0962..bd18d920 100644
--- a/examples/doc-scan/config/filesystems.php
+++ b/examples/doc-scan/config/filesystems.php
@@ -51,7 +51,7 @@
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
- 'url' => env('APP_URL').'/storage',
+ 'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],
diff --git a/examples/doc-scan/config/session.php b/examples/doc-scan/config/session.php
index d0ccd5a8..60aec7d2 100644
--- a/examples/doc-scan/config/session.php
+++ b/examples/doc-scan/config/session.php
@@ -126,7 +126,7 @@
'cookie' => env(
'SESSION_COOKIE',
- Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
+ Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'
),
/*
diff --git a/examples/doc-scan/config/yoti.php b/examples/doc-scan/config/yoti.php
index ad3f6af5..b4719eae 100644
--- a/examples/doc-scan/config/yoti.php
+++ b/examples/doc-scan/config/yoti.php
@@ -5,7 +5,7 @@
return [
'client.sdk.id' => env('YOTI_SDK_ID'),
'doc.scan.iframe.url' => (env('YOTI_DOC_SCAN_API_URL') ?: Constants::DOC_SCAN_API_URL) . '/web/index.html',
- 'pem.file.path' => (function($filePath) {
+ 'pem.file.path' => (function ($filePath) {
return strpos($filePath, '/') === 0 ? $filePath : base_path($filePath);
})(env('YOTI_KEY_FILE_PATH')),
];
diff --git a/examples/doc-scan/public/index.php b/examples/doc-scan/public/index.php
index 4584cbcd..f9ea6927 100644
--- a/examples/doc-scan/public/index.php
+++ b/examples/doc-scan/public/index.php
@@ -21,7 +21,7 @@
|
*/
-require __DIR__.'/../vendor/autoload.php';
+require __DIR__ . '/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
@@ -35,7 +35,7 @@
|
*/
-$app = require_once __DIR__.'/../bootstrap/app.php';
+$app = require_once __DIR__ . '/../bootstrap/app.php';
/*
|--------------------------------------------------------------------------
diff --git a/examples/doc-scan/resources/views/success.blade.php b/examples/doc-scan/resources/views/success.blade.php
index 0e49c218..63b0b486 100644
--- a/examples/doc-scan/resources/views/success.blade.php
+++ b/examples/doc-scan/resources/views/success.blade.php
@@ -293,7 +293,61 @@
@endif
-
+ @if (isset($sessionResult))
+ @if ($sessionResult->getIdentityProfile() != null)
+ @if ($sessionResult->getIdentityProfile()->getFailureReason() != null)
+ @if ($sessionResult->getIdentityProfile()->getFailureReason()->getReasonCode())
+
+
+
Identity Result Error
+
+
+ @if ($sessionResult->getIdentityProfile()->getFailureReason()->getReasonCode())
+
+
+
+ | Reason Code |
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getReasonCode()}}
+ |
+
+
+ | Failure Type |
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getFailureType()}}
+ |
+
+
+ | Details |
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getDetails()}}
+ |
+
+
+ | Audit Id |
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getAuditId()}}
+ |
+
+
+ | Country ISO Code |
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getDocumentCountryIsoCode()}}
+ |
+
+
+ | Document Type |
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getDocumentType()}}
+ |
+
+
+
+ @endif
+ @endif
+ @endif
+ @endif
+ @endif
@if (count($sessionResult->getResources()->getIdDocuments()) > 0)
diff --git a/examples/doc-scan/server.php b/examples/doc-scan/server.php
index 5fb6379e..7f109d96 100644
--- a/examples/doc-scan/server.php
+++ b/examples/doc-scan/server.php
@@ -14,8 +14,8 @@
// This file allows us to emulate Apache's "mod_rewrite" functionality from the
// built-in PHP web server. This provides a convenient way to test a Laravel
// application without having installed a "real" web server software here.
-if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
+if ($uri !== '/' && file_exists(__DIR__ . '/public' . $uri)) {
return false;
}
-require_once __DIR__.'/public/index.php';
+require_once __DIR__ . '/public/index.php';
diff --git a/examples/profile/resources/views/partial/report.blade.php b/examples/profile/resources/views/partial/report.blade.php
index 707abe99..d3466436 100644
--- a/examples/profile/resources/views/partial/report.blade.php
+++ b/examples/profile/resources/views/partial/report.blade.php
@@ -1,40 +1,54 @@
-@foreach ($report as $key => $value)
-
-
-
-
- {{ $key }}
- |
-
-
-
- @if (isset($value) && is_array($value))
- @foreach ($value as $name => $result)
- @if (is_array($result))
- @foreach ($result as $data => $view)
- @if (is_array($view))
- @foreach ($view as $key2 => $value2)
- @if (is_array($value2))
- {{json_encode($value2)}}
- @else
-
- {{ $key2 }} {{ $value2 }} |
-
- @endif
- @endforeach
- @else
-
- {{ $data }} {{ $view }} |
-
- @endif
- @endforeach
- @else
-
+@if (isset($key) && is_array($key))
+ @foreach ($report as $key => $value)
+
+
+
+
+ {{ $key }}
+ |
+
+
+
+ @foreach ($value as $name => $result)
+ @if (isset($result) && is_array($result))
+ @foreach ($result as $data => $view)
+ @if (is_array($view))
+ @foreach ($view as $key2 => $value2)
+ @if (is_array($value2))
+ {{json_encode($value2)}}
+ @else
+
+ {{ $key2 }} {{ $value2 }} |
+
+ @endif
+ @endforeach
+ @else
+
+ {{ $data }} {{ $view }} |
+
+ @endif
+ @endforeach
+ @else
+
{{ $name }} {{ $result }} |
-
- @endif
+
+ @endif
+ @endforeach
+
+
+
+ @endforeach
+@else
+
+ @foreach ($report as $key => $value)
+
+
+ {{ $key }}
+
+ {!! json_encode($value, JSON_PRETTY_PRINT) !!}
+
+ |
+
@endforeach
- @endif
-
- @endforeach
+@endif
\ No newline at end of file
diff --git a/src/Aml/Profile.php b/src/Aml/Profile.php
index b0ee2a10..d03219c3 100644
--- a/src/Aml/Profile.php
+++ b/src/Aml/Profile.php
@@ -50,7 +50,7 @@ class Profile implements \JsonSerializable
* @param \Yoti\Aml\Address $amlAddress
* @param null|string $ssn
*/
- public function __construct($givenNames, $familyName, Address $amlAddress, string $ssn = null)
+ public function __construct(string $givenNames, string $familyName, Address $amlAddress, ?string $ssn = null)
{
$this->givenNames = $givenNames;
$this->familyName = $familyName;
diff --git a/src/Auth/AuthenticationTokenGenerator.php b/src/Auth/AuthenticationTokenGenerator.php
new file mode 100644
index 00000000..3b267889
--- /dev/null
+++ b/src/Auth/AuthenticationTokenGenerator.php
@@ -0,0 +1,227 @@
+withSdkId($sdkId)
+ * ->withPemFile($pemFile)
+ * ->build();
+ *
+ * $response = $generator->generate(['scope1', 'scope2']);
+ * $token = $response->getAccessToken();
+ *
+ * Mirrors the Java SDK's com.yoti.auth.AuthenticationTokenGenerator.
+ */
+class AuthenticationTokenGenerator
+{
+ /**
+ * @var string
+ */
+ private $sdkId;
+
+ /**
+ * @var PemFile
+ */
+ private $pemFile;
+
+ /**
+ * @var callable
+ */
+ private $jwtIdSupplier;
+
+ /**
+ * @var string
+ */
+ private $authApiUrl;
+
+ /**
+ * @var ClientInterface
+ */
+ private $httpClient;
+
+ /**
+ * @param string $sdkId
+ * @param PemFile $pemFile
+ * @param callable $jwtIdSupplier
+ * @param string $authApiUrl
+ * @param ClientInterface|null $httpClient
+ */
+ public function __construct(
+ string $sdkId,
+ PemFile $pemFile,
+ callable $jwtIdSupplier,
+ string $authApiUrl,
+ ?ClientInterface $httpClient = null
+ ) {
+ $this->sdkId = $sdkId;
+ $this->pemFile = $pemFile;
+ $this->jwtIdSupplier = $jwtIdSupplier;
+ $this->authApiUrl = $authApiUrl;
+ $this->httpClient = $httpClient ?? new \GuzzleHttp\Client();
+ }
+
+ /**
+ * Creates a new Builder instance.
+ *
+ * @return Builder
+ */
+ public static function builder(): Builder
+ {
+ return new Builder();
+ }
+
+ /**
+ * Generate an authentication token for the supplied scopes.
+ *
+ * @param array $scopes
+ *
+ * @return CreateAuthenticationTokenResponse
+ *
+ * @throws AuthException
+ * @throws \InvalidArgumentException
+ */
+ public function generate(array $scopes): CreateAuthenticationTokenResponse
+ {
+ if (count($scopes) === 0) {
+ throw new \InvalidArgumentException('scopes must not be empty');
+ }
+
+ $jwt = $this->createSignedJwt();
+
+ $formParams = [
+ 'grant_type' => 'client_credentials',
+ 'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
+ 'scope' => implode(' ', $scopes),
+ 'client_assertion' => $jwt,
+ ];
+
+ $responseBody = $this->performFormRequest($formParams);
+
+ $responseData = json_decode($responseBody, true);
+ if (!is_array($responseData)) {
+ throw new AuthException('Failed to decode authentication token response');
+ }
+
+ return new CreateAuthenticationTokenResponse($responseData);
+ }
+
+ /**
+ * Create a PS384-signed JWT for the client assertion.
+ *
+ * Uses phpseclib3 for RSASSA-PSS (PS384) signing, since
+ * firebase/php-jwt does not support PSS algorithms.
+ *
+ * @return string
+ *
+ * @throws AuthException
+ */
+ private function createSignedJwt(): string
+ {
+ $sdkIdProperty = sprintf('sdk:%s', $this->sdkId);
+ $now = time();
+ $jwtId = ($this->jwtIdSupplier)();
+
+ $header = [
+ 'alg' => 'PS384',
+ 'typ' => 'JWT',
+ ];
+
+ $claims = [
+ 'iss' => $sdkIdProperty,
+ 'sub' => $sdkIdProperty,
+ 'jti' => $jwtId,
+ 'aud' => $this->authApiUrl,
+ 'exp' => $now + 300, // 5 minutes
+ 'iat' => $now,
+ ];
+
+ $headerEncoded = $this->base64UrlEncode((string) json_encode($header));
+ $claimsEncoded = $this->base64UrlEncode((string) json_encode($claims));
+ $signingInput = $headerEncoded . '.' . $claimsEncoded;
+
+ try {
+ /** @var \phpseclib3\Crypt\RSA\PrivateKey $rsaKey */
+ $rsaKey = \phpseclib3\Crypt\PublicKeyLoader::load((string) $this->pemFile);
+ $rsaKey = $rsaKey
+ ->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PSS)
+ ->withHash('sha384')
+ ->withMGFHash('sha384');
+ } catch (\Exception $e) {
+ throw new AuthException('Failed to load private key from PEM file: ' . $e->getMessage(), 0, $e);
+ }
+
+ $signature = $rsaKey->sign($signingInput);
+
+ return $signingInput . '.' . $this->base64UrlEncode($signature);
+ }
+
+ /**
+ * Base64url-encode a string (RFC 7515).
+ *
+ * @param string $data
+ *
+ * @return string
+ */
+ private function base64UrlEncode(string $data): string
+ {
+ return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
+ }
+
+ /**
+ * Perform an application/x-www-form-urlencoded POST request.
+ *
+ * @param array $formParams
+ *
+ * @return string
+ *
+ * @throws AuthException
+ */
+ private function performFormRequest(array $formParams): string
+ {
+ $postData = http_build_query($formParams);
+
+ $request = new \GuzzleHttp\Psr7\Request(
+ 'POST',
+ $this->authApiUrl,
+ [
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'Content-Length' => (string) strlen($postData),
+ ],
+ $postData
+ );
+
+ try {
+ $response = $this->httpClient->sendRequest($request);
+ } catch (\Psr\Http\Client\ClientExceptionInterface $e) {
+ throw new AuthException('Auth token request failed: ' . $e->getMessage(), 0, $e);
+ }
+
+ $httpCode = $response->getStatusCode();
+ $responseBody = (string) $response->getBody();
+
+ if ($httpCode >= 400) {
+ throw new AuthException(
+ sprintf(
+ 'Auth token request failed with HTTP %d: %s',
+ $httpCode,
+ $responseBody
+ )
+ );
+ }
+
+ return $responseBody;
+ }
+}
diff --git a/src/Auth/Builder.php b/src/Auth/Builder.php
new file mode 100644
index 00000000..9438c58d
--- /dev/null
+++ b/src/Auth/Builder.php
@@ -0,0 +1,190 @@
+sdkId = $sdkId;
+ return $this;
+ }
+
+ /**
+ * Sets the PEM file used for signing the JWT.
+ *
+ * @param PemFile $pemFile
+ *
+ * @return self
+ */
+ public function withPemFile(PemFile $pemFile): self
+ {
+ $this->pemFile = $pemFile;
+ return $this;
+ }
+
+ /**
+ * Sets the PEM file from a file path.
+ *
+ * @param string $filePath
+ *
+ * @return self
+ */
+ public function withPemFilePath(string $filePath): self
+ {
+ return $this->withPemFile(PemFile::fromFilePath($filePath));
+ }
+
+ /**
+ * Sets the PEM file from a string.
+ *
+ * @param string $content
+ *
+ * @return self
+ */
+ public function withPemString(string $content): self
+ {
+ return $this->withPemFile(PemFile::fromString($content));
+ }
+
+ /**
+ * Sets a callable that generates unique JWT IDs.
+ * Defaults to generating UUID v4 if not provided.
+ *
+ * @param callable $jwtIdSupplier A callable that returns a string
+ *
+ * @return self
+ */
+ public function withJwtIdSupplier(callable $jwtIdSupplier): self
+ {
+ $this->jwtIdSupplier = $jwtIdSupplier;
+ return $this;
+ }
+
+ /**
+ * Sets a custom auth API URL (primarily for testing).
+ *
+ * @param string $authApiUrl
+ *
+ * @return self
+ */
+ public function withAuthApiUrl(string $authApiUrl): self
+ {
+ $this->authApiUrl = $authApiUrl;
+ return $this;
+ }
+
+ /**
+ * Sets a custom PSR-18 HTTP client (primarily for testing).
+ *
+ * @param ClientInterface $httpClient
+ *
+ * @return self
+ */
+ public function withHttpClient(ClientInterface $httpClient): self
+ {
+ $this->httpClient = $httpClient;
+ return $this;
+ }
+
+ /**
+ * Builds the AuthenticationTokenGenerator.
+ *
+ * @return AuthenticationTokenGenerator
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function build(): AuthenticationTokenGenerator
+ {
+ if ($this->sdkId === null || $this->sdkId === '') {
+ throw new \InvalidArgumentException("'sdkId' must not be empty or null");
+ }
+
+ if ($this->pemFile === null) {
+ throw new \InvalidArgumentException("'pemFile' must not be null");
+ }
+
+ $jwtIdSupplier = $this->jwtIdSupplier ?? static function (): string {
+ return self::generateUuidV4();
+ };
+
+ // Resolve auth URL: custom > environment variable > default
+ $authApiUrl = $this->authApiUrl
+ ?? Env::get(Properties::ENV_YOTI_AUTH_URL)
+ ?? Properties::DEFAULT_YOTI_AUTH_URL;
+
+ return new AuthenticationTokenGenerator(
+ $this->sdkId,
+ $this->pemFile,
+ $jwtIdSupplier,
+ $authApiUrl,
+ $this->httpClient
+ );
+ }
+
+ /**
+ * Generate a UUID v4.
+ *
+ * @return string
+ */
+ private static function generateUuidV4(): string
+ {
+ return sprintf(
+ '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000,
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff)
+ );
+ }
+}
diff --git a/src/Auth/CreateAuthenticationTokenResponse.php b/src/Auth/CreateAuthenticationTokenResponse.php
new file mode 100644
index 00000000..b9e6adb8
--- /dev/null
+++ b/src/Auth/CreateAuthenticationTokenResponse.php
@@ -0,0 +1,88 @@
+ $responseData
+ */
+ public function __construct(array $responseData)
+ {
+ $this->accessToken = $responseData['access_token'] ?? '';
+ $this->tokenType = $responseData['token_type'] ?? '';
+ $this->expiresIn = isset($responseData['expires_in']) ? (int)$responseData['expires_in'] : null;
+ $this->scope = $responseData['scope'] ?? null;
+ }
+
+ /**
+ * Returns the Yoti Authentication token used to perform requests to other Yoti services.
+ *
+ * @return string
+ */
+ public function getAccessToken(): string
+ {
+ return $this->accessToken;
+ }
+
+ /**
+ * Returns the type of the newly generated authentication token.
+ *
+ * @return string
+ */
+ public function getTokenType(): string
+ {
+ return $this->tokenType;
+ }
+
+ /**
+ * Returns the amount of time (in seconds) in which the newly generated
+ * Authentication Token will expire.
+ *
+ * @return int|null
+ */
+ public function getExpiresIn(): ?int
+ {
+ return $this->expiresIn;
+ }
+
+ /**
+ * A whitespace delimited string of scopes that the Authentication token has.
+ *
+ * @return string|null
+ */
+ public function getScope(): ?string
+ {
+ return $this->scope;
+ }
+}
diff --git a/src/Auth/Exception/AuthException.php b/src/Auth/Exception/AuthException.php
new file mode 100644
index 00000000..3b6b8ec8
--- /dev/null
+++ b/src/Auth/Exception/AuthException.php
@@ -0,0 +1,12 @@
+id = $sdkId;
}
+ /**
+ * Returns a new Builder instance for fluent construction.
+ *
+ * @return DigitalIdentityClientBuilder
+ */
+ public static function builder(): DigitalIdentityClientBuilder
+ {
+ return new DigitalIdentityClientBuilder();
+ }
+
+ /**
+ * Internal factory used by DigitalIdentityClientBuilder to create an instance
+ * with an already-configured service.
+ *
+ * @internal
+ * @param DigitalIdentityService $service
+ * @return self
+ */
+ public static function fromService(DigitalIdentityService $service): self
+ {
+ $instance = new \ReflectionClass(self::class);
+ /** @var self $client */
+ $client = $instance->newInstanceWithoutConstructor();
+ $client->digitalIdentityService = $service;
+ $client->id = '';
+ return $client;
+ }
+
/**
* Create a sharing session to initiate a sharing process based on a policy
*
diff --git a/src/DigitalIdentityClientBuilder.php b/src/DigitalIdentityClientBuilder.php
new file mode 100644
index 00000000..0c067dea
--- /dev/null
+++ b/src/DigitalIdentityClientBuilder.php
@@ -0,0 +1,195 @@
+withClientSdkId('your-sdk-id')
+ * ->withPemFilePath('/path/to/key.pem')
+ * ->build();
+ *
+ * // Authentication token mode:
+ * $client = DigitalIdentityClient::builder()
+ * ->withAuthenticationToken('your-bearer-token')
+ * ->build();
+ * ```
+ */
+class DigitalIdentityClientBuilder
+{
+ /**
+ * @var string|null
+ */
+ private $authenticationToken;
+
+ /**
+ * @var string|null
+ */
+ private $sdkId;
+
+ /**
+ * @var PemFile|null
+ */
+ private $pemFile;
+
+ /**
+ * @var array
+ */
+ private $options = [];
+
+ /**
+ * Set the authentication token for Bearer token auth mode.
+ * Mutually exclusive with sdkId/PEM configuration.
+ *
+ * @param string $authenticationToken
+ * @return $this
+ */
+ public function withAuthenticationToken(string $authenticationToken): self
+ {
+ $this->authenticationToken = $authenticationToken;
+ return $this;
+ }
+
+ /**
+ * Set the SDK client ID for signed request auth mode.
+ *
+ * @param string $sdkId
+ * @return $this
+ */
+ public function withClientSdkId(string $sdkId): self
+ {
+ $this->sdkId = $sdkId;
+ return $this;
+ }
+
+ /**
+ * Set the PEM file for signed request auth mode.
+ *
+ * @param PemFile $pemFile
+ * @return $this
+ */
+ public function withPemFile(PemFile $pemFile): self
+ {
+ $this->pemFile = $pemFile;
+ return $this;
+ }
+
+ /**
+ * Set the PEM from a file path for signed request auth mode.
+ *
+ * @param string $pemFilePath
+ * @return $this
+ */
+ public function withPemFilePath(string $pemFilePath): self
+ {
+ $this->pemFile = PemFile::resolveFromString($pemFilePath);
+ return $this;
+ }
+
+ /**
+ * Set the PEM from a string for signed request auth mode.
+ *
+ * @param string $pemString
+ * @return $this
+ */
+ public function withPemString(string $pemString): self
+ {
+ $this->pemFile = PemFile::resolveFromString($pemString);
+ return $this;
+ }
+
+ /**
+ * Set SDK configuration options.
+ *
+ * @param array $options
+ * @return $this
+ */
+ public function withOptions(array $options): self
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * Build the DigitalIdentityClient instance.
+ *
+ * @return DigitalIdentityClient
+ * @throws \InvalidArgumentException if configuration is invalid
+ */
+ public function build(): DigitalIdentityClient
+ {
+ // Set API URL from environment variable.
+ $this->options[Config::API_URL] = $this->options[Config::API_URL]
+ ?? Env::get(Constants::ENV_DIGITAL_IDENTITY_API_URL);
+
+ $config = new Config($this->options);
+
+ if ($this->authenticationToken !== null) {
+ $this->validateAuthToken();
+ /** @var string $authToken */
+ $authToken = $this->authenticationToken;
+ $authStrategy = new BearerTokenStrategy($authToken);
+ $service = DigitalIdentityService::withAuthStrategy($authStrategy, $config);
+ return DigitalIdentityClient::fromService($service);
+ }
+
+ $this->validateForSignedRequest();
+ /** @var string $sdkId */
+ $sdkId = $this->sdkId;
+ /** @var PemFile $pemFile */
+ $pemFile = $this->pemFile;
+ $service = new DigitalIdentityService($sdkId, $pemFile, $config);
+ return DigitalIdentityClient::fromService($service);
+ }
+
+ /**
+ * Validate that sdkId and PEM are provided for signed request mode.
+ *
+ * @throws \InvalidArgumentException
+ */
+ private function validateForSignedRequest(): void
+ {
+ if ($this->sdkId === null || $this->sdkId === '' || $this->pemFile === null) {
+ throw new \InvalidArgumentException(
+ 'An sdkId and PEM file must be provided when not using an authentication token'
+ );
+ }
+ }
+
+ /**
+ * Validate that sdkId and PEM are NOT provided when using auth token mode.
+ *
+ * @throws \InvalidArgumentException
+ */
+ private function validateAuthToken(): void
+ {
+ Validation::notEmptyString($this->authenticationToken, 'Authentication token');
+
+ if ($this->sdkId !== null || $this->pemFile !== null) {
+ throw new \InvalidArgumentException(
+ 'Must not supply sdkId or PEM file when using an authentication token'
+ );
+ }
+ }
+}
diff --git a/src/DocScan/Constants.php b/src/DocScan/Constants.php
index 8551c6ba..7e5cf894 100644
--- a/src/DocScan/Constants.php
+++ b/src/DocScan/Constants.php
@@ -56,4 +56,6 @@ class Constants
public const RECLASSIFICATION = "RECLASSIFICATION";
public const GENERIC = "GENERIC";
+
+ public const VERIFY_SHARE_CODE_TASK = 'VERIFY_SHARE_CODE_TASK';
}
diff --git a/src/DocScan/DocScanClient.php b/src/DocScan/DocScanClient.php
index d2ca279f..d6bac470 100644
--- a/src/DocScan/DocScanClient.php
+++ b/src/DocScan/DocScanClient.php
@@ -66,6 +66,16 @@ public function __construct(
$this->docScanService = new Service($sdkId, $pemFile, $config);
}
+ /**
+ * Returns a new Builder instance for fluent construction.
+ *
+ * @return DocScanClientBuilder
+ */
+ public static function builder(): DocScanClientBuilder
+ {
+ return new DocScanClientBuilder();
+ }
+
/**
* Creates a session within the Yoti Doc Scan session
* using the supplied specification.
@@ -244,4 +254,21 @@ public function triggerIbvEmailNotification(string $sessionId): void
{
$this->docScanService->triggerIbvEmailNotification($sessionId);
}
+
+ /**
+ * Internal factory used by DocScanClientBuilder to create an instance
+ * with an already-configured Service.
+ *
+ * @internal
+ * @param Service $service
+ * @return self
+ */
+ public static function fromService(Service $service): self
+ {
+ $instance = new \ReflectionClass(self::class);
+ /** @var self $client */
+ $client = $instance->newInstanceWithoutConstructor();
+ $client->docScanService = $service;
+ return $client;
+ }
}
diff --git a/src/DocScan/DocScanClientBuilder.php b/src/DocScan/DocScanClientBuilder.php
new file mode 100644
index 00000000..708bb0d5
--- /dev/null
+++ b/src/DocScan/DocScanClientBuilder.php
@@ -0,0 +1,195 @@
+withClientSdkId('your-sdk-id')
+ * ->withPemFilePath('/path/to/key.pem')
+ * ->build();
+ *
+ * // Authentication token mode:
+ * $client = DocScanClient::builder()
+ * ->withAuthenticationToken('your-bearer-token')
+ * ->build();
+ * ```
+ */
+class DocScanClientBuilder
+{
+ /**
+ * @var string|null
+ */
+ private $authenticationToken;
+
+ /**
+ * @var string|null
+ */
+ private $sdkId;
+
+ /**
+ * @var PemFile|null
+ */
+ private $pemFile;
+
+ /**
+ * @var array
+ */
+ private $options = [];
+
+ /**
+ * Set the authentication token for Bearer token auth mode.
+ * Mutually exclusive with sdkId/PEM configuration.
+ *
+ * @param string $authenticationToken
+ * @return $this
+ */
+ public function withAuthenticationToken(string $authenticationToken): self
+ {
+ $this->authenticationToken = $authenticationToken;
+ return $this;
+ }
+
+ /**
+ * Set the SDK client ID for signed request auth mode.
+ *
+ * @param string $sdkId
+ * @return $this
+ */
+ public function withClientSdkId(string $sdkId): self
+ {
+ $this->sdkId = $sdkId;
+ return $this;
+ }
+
+ /**
+ * Set the PEM file for signed request auth mode.
+ *
+ * @param PemFile $pemFile
+ * @return $this
+ */
+ public function withPemFile(PemFile $pemFile): self
+ {
+ $this->pemFile = $pemFile;
+ return $this;
+ }
+
+ /**
+ * Set the PEM from a file path for signed request auth mode.
+ *
+ * @param string $pemFilePath
+ * @return $this
+ */
+ public function withPemFilePath(string $pemFilePath): self
+ {
+ $this->pemFile = PemFile::resolveFromString($pemFilePath);
+ return $this;
+ }
+
+ /**
+ * Set the PEM from a string for signed request auth mode.
+ *
+ * @param string $pemString
+ * @return $this
+ */
+ public function withPemString(string $pemString): self
+ {
+ $this->pemFile = PemFile::resolveFromString($pemString);
+ return $this;
+ }
+
+ /**
+ * Set SDK configuration options.
+ *
+ * @param array $options
+ * @return $this
+ */
+ public function withOptions(array $options): self
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * Build the DocScanClient instance.
+ *
+ * @return DocScanClient
+ * @throws \InvalidArgumentException if configuration is invalid
+ */
+ public function build(): DocScanClient
+ {
+ // Set API URL from environment variable.
+ $this->options[Config::API_URL] = $this->options[Config::API_URL]
+ ?? Env::get(Constants::ENV_DOC_SCAN_API_URL);
+
+ $config = new Config($this->options);
+
+ if ($this->authenticationToken !== null) {
+ $this->validateAuthToken();
+ /** @var string $authToken */
+ $authToken = $this->authenticationToken;
+ $authStrategy = new BearerTokenStrategy($authToken);
+ $service = Service::withAuthStrategy($authStrategy, $config);
+ return DocScanClient::fromService($service);
+ }
+
+ $this->validateForSignedRequest();
+ /** @var string $sdkId */
+ $sdkId = $this->sdkId;
+ /** @var PemFile $pemFile */
+ $pemFile = $this->pemFile;
+ $service = new Service($sdkId, $pemFile, $config);
+ return DocScanClient::fromService($service);
+ }
+
+ /**
+ * Validate that sdkId and PEM are provided for signed request mode.
+ *
+ * @throws \InvalidArgumentException
+ */
+ private function validateForSignedRequest(): void
+ {
+ if ($this->sdkId === null || $this->sdkId === '' || $this->pemFile === null) {
+ throw new \InvalidArgumentException(
+ 'An sdkId and PEM file must be provided when not using an authentication token'
+ );
+ }
+ }
+
+ /**
+ * Validate that sdkId and PEM are NOT provided when using auth token mode.
+ *
+ * @throws \InvalidArgumentException
+ */
+ private function validateAuthToken(): void
+ {
+ Validation::notEmptyString($this->authenticationToken, 'Authentication token');
+
+ if ($this->sdkId !== null || $this->pemFile !== null) {
+ throw new \InvalidArgumentException(
+ 'Must not supply sdkId or PEM file when using an authentication token'
+ );
+ }
+ }
+}
diff --git a/src/DocScan/Service.php b/src/DocScan/Service.php
index 9e57feb6..f46961e2 100644
--- a/src/DocScan/Service.php
+++ b/src/DocScan/Service.php
@@ -18,6 +18,7 @@
use Yoti\DocScan\Session\Retrieve\Instructions\ContactProfileResponse;
use Yoti\DocScan\Session\Retrieve\Instructions\InstructionsResponse;
use Yoti\DocScan\Support\SupportedDocumentsResponse;
+use Yoti\Http\AuthStrategy\AuthStrategyInterface;
use Yoti\Http\Payload;
use Yoti\Http\Request;
use Yoti\Http\RequestBuilder;
@@ -37,10 +38,15 @@ class Service
private $sdkId;
/**
- * @var PemFile
+ * @var PemFile|null
*/
private $pemFile;
+ /**
+ * @var AuthStrategyInterface|null
+ */
+ private $authStrategy;
+
/**
* @var Config
*/
@@ -64,6 +70,54 @@ public function __construct(string $sdkId, PemFile $pemFile, Config $config)
$this->apiUrl = $config->getApiUrl() ?? Constants::DOC_SCAN_API_URL;
}
+ /**
+ * Create a Service instance using an authentication strategy.
+ *
+ * When using BearerTokenStrategy (central auth), no sdkId or PEM
+ * is required since the Bearer token handles authorization.
+ *
+ * @param AuthStrategyInterface $authStrategy
+ * @param Config $config
+ *
+ * @return self
+ */
+ public static function withAuthStrategy(AuthStrategyInterface $authStrategy, Config $config): self
+ {
+ $instance = new \ReflectionClass(self::class);
+ $service = $instance->newInstanceWithoutConstructor();
+ $service->authStrategy = $authStrategy;
+ $service->config = $config;
+ $service->apiUrl = $config->getApiUrl() ?? Constants::DOC_SCAN_API_URL;
+ $service->sdkId = '';
+ return $service;
+ }
+
+ /**
+ * Apply authentication to a RequestBuilder.
+ *
+ * If an explicit auth strategy was set, uses it.
+ * Otherwise falls back to the legacy PemFile + sdkId approach.
+ *
+ * @param RequestBuilder $builder
+ * @param bool $includeSdkId Whether to include sdkId as query param (legacy mode only)
+ *
+ * @return RequestBuilder
+ */
+ private function applyAuth(RequestBuilder $builder, bool $includeSdkId = true): RequestBuilder
+ {
+ if ($this->authStrategy !== null) {
+ return $builder->withAuthStrategy($this->authStrategy);
+ }
+
+ if ($this->pemFile !== null) {
+ $builder->withPemFile($this->pemFile);
+ }
+ if ($includeSdkId && $this->sdkId !== null && $this->sdkId !== '') {
+ $builder->withQueryParam('sdkId', $this->sdkId);
+ }
+ return $builder;
+ }
+
/**
* Creates a Yoti Doc Scan session using the supplied
* specification.
@@ -76,14 +130,14 @@ public function __construct(string $sdkId, PemFile $pemFile, Config $config)
*/
public function createSession(SessionSpecification $sessionSpec): CreateSessionResult
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
->withEndpoint('/sessions')
- ->withQueryParam('sdkId', $this->sdkId)
->withPayload(Payload::fromJsonData($sessionSpec))
->withHeader('Content-Type', 'application/json')
- ->withPemFile($this->pemFile)
- ->withPost()
+ ->withPost();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -103,12 +157,12 @@ public function createSession(SessionSpecification $sessionSpec): CreateSessionR
*/
public function retrieveSession(string $sessionId): GetSessionResult
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
->withEndpoint(sprintf('/sessions/%s', $sessionId))
- ->withQueryParam('sdkId', $this->sdkId)
- ->withPemFile($this->pemFile)
- ->withGet()
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -127,12 +181,12 @@ public function retrieveSession(string $sessionId): GetSessionResult
*/
public function deleteSession(string $sessionId): void
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
->withEndpoint(sprintf('/sessions/%s', $sessionId))
- ->withQueryParam('sdkId', $this->sdkId)
- ->withPemFile($this->pemFile)
- ->withMethod(Request::METHOD_DELETE)
+ ->withMethod(Request::METHOD_DELETE);
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -150,12 +204,12 @@ public function deleteSession(string $sessionId): void
*/
public function getMediaContent(string $sessionId, string $mediaId): ?Media
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
->withEndpoint(sprintf('/sessions/%s/media/%s/content', $sessionId, $mediaId))
- ->withQueryParam('sdkId', $this->sdkId)
- ->withPemFile($this->pemFile)
- ->withGet()
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -181,12 +235,12 @@ public function getMediaContent(string $sessionId, string $mediaId): ?Media
*/
public function deleteMediaContent(string $sessionId, string $mediaId): void
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
->withEndpoint(sprintf('/sessions/%s/media/%s/content', $sessionId, $mediaId))
- ->withQueryParam('sdkId', $this->sdkId)
- ->withPemFile($this->pemFile)
- ->withMethod(Request::METHOD_DELETE)
+ ->withMethod(Request::METHOD_DELETE);
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -203,14 +257,14 @@ public function getSupportedDocuments(bool $isStrictlyLatin): SupportedDocuments
$requestBuilder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
->withEndpoint('/supported-documents')
- ->withPemFile($this->pemFile)
->withGet();
if ($isStrictlyLatin) {
$requestBuilder->withQueryParam('includeNonLatin', '1');
}
- $response = $requestBuilder
+ // getSupportedDocuments does not require sdkId in legacy mode
+ $response = $this->applyAuth($requestBuilder, false)
->build()
->execute();
@@ -231,13 +285,13 @@ public function createFaceCaptureResource(
string $sessionId,
CreateFaceCaptureResourcePayload $createFaceCaptureResourcePayload
): CreateFaceCaptureResourceResponse {
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
- ->withQueryParam('sdkId', $this->sdkId)
->withEndpoint("sessions/$sessionId/resources/face-capture")
- ->withPemFile($this->pemFile)
->withPayload(Payload::fromJsonData($createFaceCaptureResourcePayload))
- ->withPost()
+ ->withPost();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -259,7 +313,7 @@ public function uploadFaceCaptureImage(
string $resourceId,
UploadFaceCaptureImagePayload $faceCaptureImagePayload
): void {
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withMultipartBoundary(Config::YOTI_MULTIPART_BOUNDARY)
->withMultipartBinaryBody(
"binary-content",
@@ -267,11 +321,11 @@ public function uploadFaceCaptureImage(
$faceCaptureImagePayload->getImageContentType(),
'face-capture-image'
)
- ->withPemFile($this->pemFile)
->withBaseUrl($this->apiUrl)
- ->withQueryParam('sdkId', $this->sdkId)
->withEndpoint("/sessions/$sessionId/resources/face-capture/$resourceId/image")
- ->withPut()
+ ->withPut();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -285,12 +339,12 @@ public function uploadFaceCaptureImage(
*/
public function fetchSessionConfiguration(string $sessionId): SessionConfigurationResponse
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
->withEndpoint(sprintf('/sessions/%s/configuration', $sessionId))
- ->withQueryParam('sdkId', $this->sdkId)
- ->withPemFile($this->pemFile)
- ->withGet()
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -308,12 +362,13 @@ public function fetchSessionConfiguration(string $sessionId): SessionConfigurati
*/
public function putIbvInstructions(string $sessionId, Instructions $instructions): void
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
- ->withPemFile($this->pemFile)
->withEndpoint(sprintf('/sessions/%s/instructions', $sessionId))
->withPut()
- ->withPayload(Payload::fromJsonData($instructions))
+ ->withPayload(Payload::fromJsonData($instructions));
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -327,11 +382,12 @@ public function putIbvInstructions(string $sessionId, Instructions $instructions
*/
public function getIbvInstructions(string $sessionId): InstructionsResponse
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
- ->withPemFile($this->pemFile)
->withEndpoint(sprintf('/sessions/%s/instructions', $sessionId))
- ->withGet()
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -349,11 +405,12 @@ public function getIbvInstructions(string $sessionId): InstructionsResponse
*/
public function getIbvInstructionsPdf(string $sessionId): Media
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
- ->withPemFile($this->pemFile)
->withEndpoint(sprintf('/sessions/%s/instructions/pdf', $sessionId))
- ->withGet()
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -372,11 +429,12 @@ public function getIbvInstructionsPdf(string $sessionId): Media
*/
public function fetchInstructionsContactProfile(string $sessionId): ContactProfileResponse
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
- ->withPemFile($this->pemFile)
->withEndpoint(sprintf('/sessions/%s/instructions/contact-profile', $sessionId))
- ->withGet()
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -393,11 +451,12 @@ public function fetchInstructionsContactProfile(string $sessionId): ContactProfi
*/
public function triggerIbvEmailNotification(string $sessionId): void
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->apiUrl)
- ->withPemFile($this->pemFile)
->withEndpoint(sprintf('/sessions/%s/instructions/email', $sessionId))
- ->withPost()
+ ->withPost();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
diff --git a/src/DocScan/Session/Create/ApplicantProfile.php b/src/DocScan/Session/Create/ApplicantProfile.php
new file mode 100644
index 00000000..0eb26dc0
--- /dev/null
+++ b/src/DocScan/Session/Create/ApplicantProfile.php
@@ -0,0 +1,95 @@
+fullName = $fullName;
+ $this->dateOfBirth = $dateOfBirth;
+ $this->namePrefix = $namePrefix;
+ $this->structuredPostalAddress = $structuredPostalAddress;
+ }
+
+ /**
+ * @return stdClass
+ */
+ public function jsonSerialize(): stdClass
+ {
+ return (object) Json::withoutNullValues([
+ 'full_name' => $this->fullName,
+ 'date_of_birth' => $this->dateOfBirth,
+ 'name_prefix' => $this->namePrefix,
+ 'structured_postal_address' => $this->structuredPostalAddress,
+ ]);
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getFullName(): ?string
+ {
+ return $this->fullName;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getDateOfBirth(): ?string
+ {
+ return $this->dateOfBirth;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getNamePrefix(): ?string
+ {
+ return $this->namePrefix;
+ }
+
+ /**
+ * @return StructuredPostalAddress|null
+ */
+ public function getStructuredPostalAddress(): ?StructuredPostalAddress
+ {
+ return $this->structuredPostalAddress;
+ }
+}
diff --git a/src/DocScan/Session/Create/ApplicantProfileBuilder.php b/src/DocScan/Session/Create/ApplicantProfileBuilder.php
new file mode 100644
index 00000000..e0c85d3d
--- /dev/null
+++ b/src/DocScan/Session/Create/ApplicantProfileBuilder.php
@@ -0,0 +1,81 @@
+fullName = $fullName;
+ return $this;
+ }
+
+ /**
+ * @param string $dateOfBirth
+ * @return $this
+ */
+ public function withDateOfBirth(string $dateOfBirth): self
+ {
+ $this->dateOfBirth = $dateOfBirth;
+ return $this;
+ }
+
+ /**
+ * @param string $namePrefix
+ * @return $this
+ */
+ public function withNamePrefix(string $namePrefix): self
+ {
+ $this->namePrefix = $namePrefix;
+ return $this;
+ }
+
+ /**
+ * @param StructuredPostalAddress $structuredPostalAddress
+ * @return $this
+ */
+ public function withStructuredPostalAddress(StructuredPostalAddress $structuredPostalAddress): self
+ {
+ $this->structuredPostalAddress = $structuredPostalAddress;
+ return $this;
+ }
+
+ /**
+ * @return ApplicantProfile
+ */
+ public function build(): ApplicantProfile
+ {
+ return new ApplicantProfile(
+ $this->fullName,
+ $this->dateOfBirth,
+ $this->namePrefix,
+ $this->structuredPostalAddress
+ );
+ }
+}
diff --git a/src/DocScan/Session/Create/Check/RequestedLivenessConfig.php b/src/DocScan/Session/Create/Check/RequestedLivenessConfig.php
index 050655db..07db4fea 100644
--- a/src/DocScan/Session/Create/Check/RequestedLivenessConfig.php
+++ b/src/DocScan/Session/Create/Check/RequestedLivenessConfig.php
@@ -23,7 +23,7 @@ class RequestedLivenessConfig implements RequestedCheckConfigInterface
*/
private $manualCheck;
- public function __construct(string $livenessType, int $maxRetries, string $manualCheck = null)
+ public function __construct(string $livenessType, int $maxRetries, ?string $manualCheck = null)
{
$this->livenessType = $livenessType;
$this->maxRetries = $maxRetries;
diff --git a/src/DocScan/Session/Create/ImportTokenBuilder.php b/src/DocScan/Session/Create/ImportTokenBuilder.php
index 84340213..b40dd9bf 100644
--- a/src/DocScan/Session/Create/ImportTokenBuilder.php
+++ b/src/DocScan/Session/Create/ImportTokenBuilder.php
@@ -10,7 +10,7 @@ class ImportTokenBuilder
private int $ttl;
- public function withTtl(int $ttl = null): ImportTokenBuilder
+ public function withTtl(?int $ttl = null): ImportTokenBuilder
{
$this->ttl = $ttl ?? self::DEFAULT_TTL;
diff --git a/src/DocScan/Session/Create/ResourceCreationContainer.php b/src/DocScan/Session/Create/ResourceCreationContainer.php
new file mode 100644
index 00000000..ebbbd776
--- /dev/null
+++ b/src/DocScan/Session/Create/ResourceCreationContainer.php
@@ -0,0 +1,43 @@
+applicantProfile = $applicantProfile;
+ }
+
+ /**
+ * @return stdClass
+ */
+ public function jsonSerialize(): stdClass
+ {
+ return (object) Json::withoutNullValues([
+ 'applicant_profile' => $this->applicantProfile,
+ ]);
+ }
+
+ /**
+ * @return ApplicantProfile|null
+ */
+ public function getApplicantProfile(): ?ApplicantProfile
+ {
+ return $this->applicantProfile;
+ }
+}
diff --git a/src/DocScan/Session/Create/ResourceCreationContainerBuilder.php b/src/DocScan/Session/Create/ResourceCreationContainerBuilder.php
new file mode 100644
index 00000000..9ea9e394
--- /dev/null
+++ b/src/DocScan/Session/Create/ResourceCreationContainerBuilder.php
@@ -0,0 +1,33 @@
+applicantProfile = $applicantProfile;
+ return $this;
+ }
+
+ /**
+ * @return ResourceCreationContainer
+ */
+ public function build(): ResourceCreationContainer
+ {
+ return new ResourceCreationContainer(
+ $this->applicantProfile
+ );
+ }
+}
diff --git a/src/DocScan/Session/Create/SdkConfig.php b/src/DocScan/Session/Create/SdkConfig.php
index a3c8086d..60b43fb7 100644
--- a/src/DocScan/Session/Create/SdkConfig.php
+++ b/src/DocScan/Session/Create/SdkConfig.php
@@ -68,6 +68,26 @@ class SdkConfig implements \JsonSerializable
*/
private $biometricConsentFlow;
+ /**
+ * @var string|null
+ */
+ private $darkMode;
+
+ /**
+ * @var string|null
+ */
+ private $primaryColourDarkMode;
+
+ /**
+ * @var string|null
+ */
+ private $brandId;
+
+ /**
+ * @var array|null
+ */
+ private $suppressedScreens;
+
/**
* @param string|null $allowedCaptureMethods
* @param string|null $primaryColour
@@ -81,6 +101,10 @@ class SdkConfig implements \JsonSerializable
* @param bool|null $allowHandoff
* @param array|null $idDocumentTextDataExtractionRetriesConfig
* @param string|null $biometricConsentFlow
+ * @param string|null $darkMode
+ * @param string|null $primaryColourDarkMode
+ * @param string|null $brandId
+ * @param array|null $suppressedScreens
*/
public function __construct(
?string $allowedCaptureMethods,
@@ -94,7 +118,11 @@ public function __construct(
?string $privacyPolicyUrl = null,
?bool $allowHandoff = null,
?array $idDocumentTextDataExtractionRetriesConfig = null,
- ?string $biometricConsentFlow = null
+ ?string $biometricConsentFlow = null,
+ ?string $darkMode = null,
+ ?string $primaryColourDarkMode = null,
+ ?string $brandId = null,
+ ?array $suppressedScreens = null
) {
$this->allowedCaptureMethods = $allowedCaptureMethods;
$this->primaryColour = $primaryColour;
@@ -110,6 +138,10 @@ public function __construct(
$this->attemptsConfiguration = new AttemptsConfiguration($idDocumentTextDataExtractionRetriesConfig);
}
$this->biometricConsentFlow = $biometricConsentFlow;
+ $this->darkMode = $darkMode;
+ $this->primaryColourDarkMode = $primaryColourDarkMode;
+ $this->brandId = $brandId;
+ $this->suppressedScreens = $suppressedScreens;
}
/**
@@ -129,7 +161,11 @@ public function jsonSerialize(): \stdClass
'privacy_policy_url' => $this->getPrivacyPolicyUrl(),
'allow_handoff' => $this->getAllowHandoff(),
'attempts_configuration' => $this->getAttemptsConfiguration(),
- 'biometric_consent_flow' => $this->getBiometricConsentFlow()
+ 'biometric_consent_flow' => $this->getBiometricConsentFlow(),
+ 'dark_mode' => $this->getDarkMode(),
+ 'primary_colour_dark_mode' => $this->getPrimaryColourDarkMode(),
+ 'brand_id' => $this->getBrandId(),
+ 'suppressed_screens' => $this->getSuppressedScreens()
]);
}
@@ -228,4 +264,36 @@ public function getBiometricConsentFlow(): ?string
{
return $this->biometricConsentFlow;
}
+
+ /**
+ * @return string|null
+ */
+ public function getDarkMode(): ?string
+ {
+ return $this->darkMode;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPrimaryColourDarkMode(): ?string
+ {
+ return $this->primaryColourDarkMode;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getBrandId(): ?string
+ {
+ return $this->brandId;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getSuppressedScreens(): ?array
+ {
+ return $this->suppressedScreens;
+ }
}
diff --git a/src/DocScan/Session/Create/SdkConfigBuilder.php b/src/DocScan/Session/Create/SdkConfigBuilder.php
index acf30fcc..178fb241 100644
--- a/src/DocScan/Session/Create/SdkConfigBuilder.php
+++ b/src/DocScan/Session/Create/SdkConfigBuilder.php
@@ -71,6 +71,26 @@ class SdkConfigBuilder
*/
private $biometricConsentFlow;
+ /**
+ * @var string|null
+ */
+ private $darkMode;
+
+ /**
+ * @var string|null
+ */
+ private $primaryColourDarkMode;
+
+ /**
+ * @var string|null
+ */
+ private $brandId;
+
+ /**
+ * @var array|null
+ */
+ private $suppressedScreens;
+
public function withAllowsCamera(): self
{
return $this->withAllowedCaptureMethod(self::CAMERA);
@@ -146,6 +166,7 @@ public function withBiometricConsentFlow(string $biometricConsentFlow): self
$this->biometricConsentFlow = $biometricConsentFlow;
return $this;
}
+
/**
* Allows configuring the number of attempts permitted for text extraction on an ID document
*
@@ -199,6 +220,68 @@ public function withIdDocumentTextExtractionGenericAttempts(int $genericRetries)
return $this;
}
+ public function withDarkMode(string $darkMode): self
+ {
+ $this->darkMode = $darkMode;
+ return $this;
+ }
+
+ public function withDarkModeOn(): self
+ {
+ $this->darkMode = "ON";
+ return $this;
+ }
+
+ public function withDarkModeOff(): self
+ {
+ $this->darkMode = "OFF";
+ return $this;
+ }
+
+ public function withDarkModeAuto(): self
+ {
+ $this->darkMode = "AUTO";
+ return $this;
+ }
+
+ public function withPrimaryColourDarkMode(string $primaryColourDarkMode): self
+ {
+ $this->primaryColourDarkMode = $primaryColourDarkMode;
+ return $this;
+ }
+
+ public function withBrandId(string $brandId): self
+ {
+ $this->brandId = $brandId;
+ return $this;
+ }
+
+ /**
+ * Sets the suppressed screens array for configuration
+ *
+ * @param array $suppressedScreens Array of screen identifiers to suppress
+ * @return $this
+ */
+ public function withSuppressedScreens(array $suppressedScreens): self
+ {
+ $this->suppressedScreens = $suppressedScreens;
+ return $this;
+ }
+
+ /**
+ * Adds a single screen to the suppressed screens list
+ *
+ * @param string $screenIdentifier The screen identifier to suppress
+ * @return $this
+ */
+ public function withSuppressedScreen(string $screenIdentifier): self
+ {
+ if ($this->suppressedScreens === null) {
+ $this->suppressedScreens = [];
+ }
+ $this->suppressedScreens[] = $screenIdentifier;
+ return $this;
+ }
public function build(): SdkConfig
{
@@ -214,7 +297,11 @@ public function build(): SdkConfig
$this->privacyPolicyUrl,
$this->allowHandoff,
$this->idDocumentTextDataExtractionRetriesConfig,
- $this->biometricConsentFlow
+ $this->biometricConsentFlow,
+ $this->darkMode,
+ $this->primaryColourDarkMode,
+ $this->brandId,
+ $this->suppressedScreens
);
}
}
diff --git a/src/DocScan/Session/Create/SessionSpecification.php b/src/DocScan/Session/Create/SessionSpecification.php
index 46ab1f4a..060245d7 100644
--- a/src/DocScan/Session/Create/SessionSpecification.php
+++ b/src/DocScan/Session/Create/SessionSpecification.php
@@ -78,6 +78,11 @@ class SessionSpecification implements JsonSerializable
*/
private $identityProfileRequirements;
+ /**
+ * @var object|null
+ */
+ private $advancedIdentityProfileRequirements;
+
private ?bool $createIdentityProfilePreview;
/**
@@ -85,6 +90,11 @@ class SessionSpecification implements JsonSerializable
*/
private $importToken;
+ /**
+ * @var ResourceCreationContainer|null
+ */
+ private $resources;
+
/**
* @param int|null $clientSessionTokenTtl
* @param string|null $sessionDeadline
@@ -99,8 +109,10 @@ class SessionSpecification implements JsonSerializable
* @param IbvOptions|null $ibvOptions
* @param object|null $subject
* @param object|null $identityProfileRequirements
+ * @param object|null $advancedIdentityProfileRequirements
* @param bool|null $createIdentityProfilePreview
* @param ImportToken|null $importToken
+ * @param ResourceCreationContainer|null $resources
*/
public function __construct(
?int $clientSessionTokenTtl,
@@ -116,8 +128,10 @@ public function __construct(
?IbvOptions $ibvOptions = null,
?object $subject = null,
?object $identityProfileRequirements = null,
+ ?object $advancedIdentityProfileRequirements = null,
?bool $createIdentityProfilePreview = null,
- ?ImportToken $importToken = null
+ ?ImportToken $importToken = null,
+ ?ResourceCreationContainer $resources = null
) {
$this->clientSessionTokenTtl = $clientSessionTokenTtl;
$this->sessionDeadline = $sessionDeadline;
@@ -132,8 +146,10 @@ public function __construct(
$this->ibvOptions = $ibvOptions;
$this->subject = $subject;
$this->identityProfileRequirements = $identityProfileRequirements;
+ $this->advancedIdentityProfileRequirements = $advancedIdentityProfileRequirements;
$this->createIdentityProfilePreview = $createIdentityProfilePreview;
$this->importToken = $importToken;
+ $this->resources = $resources;
}
/**
@@ -155,8 +171,10 @@ public function jsonSerialize(): stdClass
'ibv_options' => $this->getIbvOptions(),
'subject' => $this->getSubject(),
'identity_profile_requirements' => $this->getIdentityProfileRequirements(),
+ 'advanced_identity_profile_requirements' => $this->getAdvancedIdentityProfileRequirements(),
'create_identity_profile_preview' => $this->getCreateIdentityProfilePreview(),
'import_token' => $this->getImportToken(),
+ 'resources' => $this->getResources(),
]);
}
@@ -269,6 +287,14 @@ public function getIdentityProfileRequirements(): ?object
return $this->identityProfileRequirements;
}
+ /**
+ * @return object|null
+ */
+ public function getAdvancedIdentityProfileRequirements(): ?object
+ {
+ return $this->advancedIdentityProfileRequirements;
+ }
+
public function getCreateIdentityProfilePreview(): ?bool
{
return $this->createIdentityProfilePreview;
@@ -278,4 +304,12 @@ public function getImportToken(): ?ImportToken
{
return $this->importToken;
}
+
+ /**
+ * @return ResourceCreationContainer|null
+ */
+ public function getResources(): ?ResourceCreationContainer
+ {
+ return $this->resources;
+ }
}
diff --git a/src/DocScan/Session/Create/SessionSpecificationBuilder.php b/src/DocScan/Session/Create/SessionSpecificationBuilder.php
index bb54de9e..2348a16e 100644
--- a/src/DocScan/Session/Create/SessionSpecificationBuilder.php
+++ b/src/DocScan/Session/Create/SessionSpecificationBuilder.php
@@ -78,11 +78,21 @@ class SessionSpecificationBuilder
*/
private $identityProfileRequirements;
+ /**
+ * @var object|null
+ */
+ private $advancedIdentityProfileRequirements;
+
/**
* @var ImportToken|null
*/
private $importToken;
+ /**
+ * @var ResourceCreationContainer|null
+ */
+ private $resources;
+
/**
* @var bool
*/
@@ -254,6 +264,19 @@ public function withIdentityProfileRequirements($identityProfileRequirements): s
return $this;
}
+ /**
+ * Sets the Advanced Identity Profile Requirements for the session
+ *
+ * @param object $advancedIdentityProfileRequirements
+ *
+ * @return $this
+ */
+ public function withAdvancedIdentityProfileRequirements($advancedIdentityProfileRequirements): self
+ {
+ $this->advancedIdentityProfileRequirements = $advancedIdentityProfileRequirements;
+ return $this;
+ }
+
/**
* @return $this
*/
@@ -274,6 +297,19 @@ public function withImportToken($importToken): self
return $this;
}
+ /**
+ * Sets the resources for the session
+ *
+ * @param ResourceCreationContainer $resources
+ *
+ * @return $this
+ */
+ public function withResources(ResourceCreationContainer $resources): self
+ {
+ $this->resources = $resources;
+ return $this;
+ }
+
/**
* @return SessionSpecification
*/
@@ -293,8 +329,10 @@ public function build(): SessionSpecification
$this->ibvOptions,
$this->subject,
$this->identityProfileRequirements,
+ $this->advancedIdentityProfileRequirements,
$this->createIdentityProfilePreview,
$this->importToken,
+ $this->resources,
);
}
}
diff --git a/src/DocScan/Session/Create/StructuredPostalAddress.php b/src/DocScan/Session/Create/StructuredPostalAddress.php
new file mode 100644
index 00000000..bb915e4f
--- /dev/null
+++ b/src/DocScan/Session/Create/StructuredPostalAddress.php
@@ -0,0 +1,163 @@
+addressFormat = $addressFormat;
+ $this->buildingNumber = $buildingNumber;
+ $this->addressLine1 = $addressLine1;
+ $this->townCity = $townCity;
+ $this->postalCode = $postalCode;
+ $this->countryIso = $countryIso;
+ $this->country = $country;
+ $this->formattedAddress = $formattedAddress;
+ }
+
+ /**
+ * @return stdClass
+ */
+ public function jsonSerialize(): stdClass
+ {
+ return (object) Json::withoutNullValues([
+ 'address_format' => $this->addressFormat,
+ 'building_number' => $this->buildingNumber,
+ 'address_line1' => $this->addressLine1,
+ 'town_city' => $this->townCity,
+ 'postal_code' => $this->postalCode,
+ 'country_iso' => $this->countryIso,
+ 'country' => $this->country,
+ 'formatted_address' => $this->formattedAddress,
+ ]);
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getAddressFormat(): ?int
+ {
+ return $this->addressFormat;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getBuildingNumber(): ?string
+ {
+ return $this->buildingNumber;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getAddressLine1(): ?string
+ {
+ return $this->addressLine1;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getTownCity(): ?string
+ {
+ return $this->townCity;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPostalCode(): ?string
+ {
+ return $this->postalCode;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCountryIso(): ?string
+ {
+ return $this->countryIso;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCountry(): ?string
+ {
+ return $this->country;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getFormattedAddress(): ?string
+ {
+ return $this->formattedAddress;
+ }
+}
diff --git a/src/DocScan/Session/Create/StructuredPostalAddressBuilder.php b/src/DocScan/Session/Create/StructuredPostalAddressBuilder.php
new file mode 100644
index 00000000..3750c25f
--- /dev/null
+++ b/src/DocScan/Session/Create/StructuredPostalAddressBuilder.php
@@ -0,0 +1,145 @@
+addressFormat = $addressFormat;
+ return $this;
+ }
+
+ /**
+ * @param string $buildingNumber
+ * @return $this
+ */
+ public function withBuildingNumber(string $buildingNumber): self
+ {
+ $this->buildingNumber = $buildingNumber;
+ return $this;
+ }
+
+ /**
+ * @param string $addressLine1
+ * @return $this
+ */
+ public function withAddressLine1(string $addressLine1): self
+ {
+ $this->addressLine1 = $addressLine1;
+ return $this;
+ }
+
+ /**
+ * @param string $townCity
+ * @return $this
+ */
+ public function withTownCity(string $townCity): self
+ {
+ $this->townCity = $townCity;
+ return $this;
+ }
+
+ /**
+ * @param string $postalCode
+ * @return $this
+ */
+ public function withPostalCode(string $postalCode): self
+ {
+ $this->postalCode = $postalCode;
+ return $this;
+ }
+
+ /**
+ * @param string $countryIso
+ * @return $this
+ */
+ public function withCountryIso(string $countryIso): self
+ {
+ $this->countryIso = $countryIso;
+ return $this;
+ }
+
+ /**
+ * @param string $country
+ * @return $this
+ */
+ public function withCountry(string $country): self
+ {
+ $this->country = $country;
+ return $this;
+ }
+
+ /**
+ * @param string $formattedAddress
+ * @return $this
+ */
+ public function withFormattedAddress(string $formattedAddress): self
+ {
+ $this->formattedAddress = $formattedAddress;
+ return $this;
+ }
+
+ /**
+ * @return StructuredPostalAddress
+ */
+ public function build(): StructuredPostalAddress
+ {
+ return new StructuredPostalAddress(
+ $this->addressFormat,
+ $this->buildingNumber,
+ $this->addressLine1,
+ $this->townCity,
+ $this->postalCode,
+ $this->countryIso,
+ $this->country,
+ $this->formattedAddress
+ );
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/AdvancedIdentityProfilePreviewResponse.php b/src/DocScan/Session/Retrieve/AdvancedIdentityProfilePreviewResponse.php
new file mode 100644
index 00000000..ec1f2ce3
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/AdvancedIdentityProfilePreviewResponse.php
@@ -0,0 +1,27 @@
+ $sessionData
+ * @throws \Yoti\Exception\DateTimeException
+ */
+ public function __construct(array $sessionData)
+ {
+ if (isset($sessionData['media'])) {
+ $this->media = new MediaResponse($sessionData['media']);
+ }
+ }
+
+ /**
+ * @return MediaResponse|null
+ */
+ public function getMedia(): ?MediaResponse
+ {
+ return $this->media;
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/AdvancedIdentityProfileResponse.php b/src/DocScan/Session/Retrieve/AdvancedIdentityProfileResponse.php
new file mode 100644
index 00000000..5e70b5f3
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/AdvancedIdentityProfileResponse.php
@@ -0,0 +1,77 @@
+ $sessionData
+ */
+ public function __construct(array $sessionData)
+ {
+ $this->subjectId = $sessionData['subject_id'] ?? '';
+ $this->result = $sessionData['result'];
+
+ if (isset($sessionData['failure_reason'])) {
+ $this->failureReason = new FailureReasonResponse($sessionData['failure_reason']);
+ }
+
+ if (isset($sessionData['identity_profile_report'])) {
+ $this->identityProfileReport = (object)$sessionData['identity_profile_report'];
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getSubjectId(): string
+ {
+ return $this->subjectId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getResult(): string
+ {
+ return $this->result;
+ }
+
+ /**
+ * @return FailureReasonResponse|null
+ */
+ public function getFailureReason(): ?FailureReasonResponse
+ {
+ return $this->failureReason;
+ }
+
+ /**
+ * @return object|null
+ */
+ public function getIdentityProfileReport(): ?object
+ {
+ return $this->identityProfileReport;
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/ApplicantProfileResourceResponse.php b/src/DocScan/Session/Retrieve/ApplicantProfileResourceResponse.php
new file mode 100644
index 00000000..35dc108b
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/ApplicantProfileResourceResponse.php
@@ -0,0 +1,70 @@
+ $applicantProfile
+ * @throws DateTimeException
+ */
+ public function __construct(array $applicantProfile)
+ {
+ parent::__construct($applicantProfile);
+
+ if (isset($applicantProfile['media'])) {
+ $this->media = new MediaResponse($applicantProfile['media']);
+ }
+
+ $this->createdAt = isset($applicantProfile['created_at'])
+ ? DateTime::stringToDateTime($applicantProfile['created_at']) : null;
+
+ $this->lastUpdated = isset($applicantProfile['last_updated'])
+ ? DateTime::stringToDateTime($applicantProfile['last_updated']) : null;
+ }
+
+ /**
+ * @return MediaResponse|null
+ */
+ public function getMedia(): ?MediaResponse
+ {
+ return $this->media;
+ }
+
+ /**
+ * @return \DateTime|null
+ */
+ public function getCreatedAt(): ?\DateTime
+ {
+ return $this->createdAt;
+ }
+
+ /**
+ * @return \DateTime|null
+ */
+ public function getLastUpdated(): ?\DateTime
+ {
+ return $this->lastUpdated;
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/BreakdownResponse.php b/src/DocScan/Session/Retrieve/BreakdownResponse.php
index 8e9ba9c3..410e635c 100644
--- a/src/DocScan/Session/Retrieve/BreakdownResponse.php
+++ b/src/DocScan/Session/Retrieve/BreakdownResponse.php
@@ -16,6 +16,11 @@ class BreakdownResponse
*/
private $result;
+ /**
+ * @var string|null
+ */
+ private $process;
+
/**
* @var DetailsResponse[]
*/
@@ -29,6 +34,7 @@ public function __construct(array $breakdown)
{
$this->subCheck = $breakdown['sub_check'] ?? null;
$this->result = $breakdown['result'] ?? null;
+ $this->process = $breakdown['process'] ?? null;
if (isset($breakdown['details'])) {
foreach ($breakdown['details'] as $detail) {
@@ -53,6 +59,14 @@ public function getResult(): ?string
return $this->result;
}
+ /**
+ * @return string|null
+ */
+ public function getProcess(): ?string
+ {
+ return $this->process;
+ }
+
/**
* @return DetailsResponse[]
*/
diff --git a/src/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponse.php b/src/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponse.php
index 3f935a80..2b3f16d0 100644
--- a/src/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponse.php
+++ b/src/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponse.php
@@ -26,6 +26,11 @@ class SessionConfigurationResponse
*/
private $capture;
+ /**
+ * @var array|null
+ */
+ private $sdkConfig;
+
/**
* @param array $sessionData
*/
@@ -35,6 +40,7 @@ public function __construct(array $sessionData)
$this->sessionId = $sessionData['session_id'] ?? null;
$this->requestedChecks = $sessionData['requested_checks'] ?? null;
$this->capture = isset($sessionData['capture']) ? new CaptureResponse($sessionData['capture']) : null;
+ $this->sdkConfig = $sessionData['sdk_config'] ?? null;
}
/**
@@ -79,4 +85,27 @@ public function getCapture(): ?CaptureResponse
{
return $this->capture;
}
+
+ /**
+ * Returns the SDK configuration for the session
+ *
+ * @return array|null
+ */
+ public function getSdkConfig(): ?array
+ {
+ return $this->sdkConfig;
+ }
+
+ /**
+ * Returns the suppressed screens configuration if present in the SDK config
+ *
+ * @return array|null
+ */
+ public function getSuppressedScreens(): ?array
+ {
+ if ($this->sdkConfig === null) {
+ return null;
+ }
+ return $this->sdkConfig['suppressed_screens'] ?? null;
+ }
}
diff --git a/src/DocScan/Session/Retrieve/CustomAccountWatchlistCaSearchConfigResponse.php b/src/DocScan/Session/Retrieve/CustomAccountWatchlistCaSearchConfigResponse.php
index efa7819d..6b95fd6c 100644
--- a/src/DocScan/Session/Retrieve/CustomAccountWatchlistCaSearchConfigResponse.php
+++ b/src/DocScan/Session/Retrieve/CustomAccountWatchlistCaSearchConfigResponse.php
@@ -35,7 +35,9 @@ public function __construct(array $searchConfig)
$this->apiKey = $searchConfig['api_key'];
$this->monitoring = $searchConfig['monitoring'];
$this->clientRef = $searchConfig['client_ref'];
- $this->tags = array_key_exists('tags', $searchConfig) ? json_decode($searchConfig['tags'], true) : [];
+ $this->tags = array_key_exists('tags', $searchConfig) && is_string($searchConfig['tags'])
+ ? json_decode($searchConfig['tags'], true)
+ : (array_key_exists('tags', $searchConfig) && is_array($searchConfig['tags']) ? $searchConfig['tags'] : []);
}
/**
diff --git a/src/DocScan/Session/Retrieve/GetSessionResult.php b/src/DocScan/Session/Retrieve/GetSessionResult.php
index 919053c3..16e3f788 100644
--- a/src/DocScan/Session/Retrieve/GetSessionResult.php
+++ b/src/DocScan/Session/Retrieve/GetSessionResult.php
@@ -53,6 +53,10 @@ class GetSessionResult
private ?IdentityProfilePreviewResponse $identityProfilePreview;
+ private ?AdvancedIdentityProfileResponse $advancedIdentityProfile = null;
+
+ private ?AdvancedIdentityProfilePreviewResponse $advancedIdentityProfilePreview = null;
+
private ?ImportTokenResponse $importToken;
/**
@@ -93,6 +97,18 @@ public function __construct(array $sessionData)
);
}
+ if (isset($sessionData['advanced_identity_profile'])) {
+ $this->advancedIdentityProfile = new AdvancedIdentityProfileResponse(
+ $sessionData['advanced_identity_profile']
+ );
+ }
+
+ if (isset($sessionData['advanced_identity_profile_preview'])) {
+ $this->advancedIdentityProfilePreview = new AdvancedIdentityProfilePreviewResponse(
+ $sessionData['advanced_identity_profile_preview']
+ );
+ }
+
if (isset($sessionData['import_token'])) {
$this->importToken = new ImportTokenResponse($sessionData['import_token']);
}
@@ -313,7 +329,11 @@ function ($checkResponse) use ($class): bool {
public function getIdentityProfile(): ?IdentityProfileResponse
{
- return $this->identityProfile;
+ if (isset($this->identityProfile)) {
+ return $this->identityProfile;
+ } else {
+ return null;
+ }
}
public function getIdentityProfilePreview(): ?IdentityProfilePreviewResponse
@@ -321,6 +341,20 @@ public function getIdentityProfilePreview(): ?IdentityProfilePreviewResponse
return $this->identityProfilePreview;
}
+ public function getAdvancedIdentityProfile(): ?AdvancedIdentityProfileResponse
+ {
+ if (isset($this->advancedIdentityProfile)) {
+ return $this->advancedIdentityProfile;
+ } else {
+ return null;
+ }
+ }
+
+ public function getAdvancedIdentityProfilePreview(): ?AdvancedIdentityProfilePreviewResponse
+ {
+ return $this->advancedIdentityProfilePreview;
+ }
+
public function getImportToken(): ?ImportTokenResponse
{
return $this->importToken;
diff --git a/src/DocScan/Session/Retrieve/IdentityProfileResponse.php b/src/DocScan/Session/Retrieve/IdentityProfileResponse.php
index 17643e89..06bea9c2 100644
--- a/src/DocScan/Session/Retrieve/IdentityProfileResponse.php
+++ b/src/DocScan/Session/Retrieve/IdentityProfileResponse.php
@@ -31,7 +31,7 @@ class IdentityProfileResponse
*/
public function __construct(array $sessionData)
{
- $this->subjectId = $sessionData['subject_id'];
+ $this->subjectId = $sessionData['subject_id'] ?? '';
$this->result = $sessionData['result'];
if (isset($sessionData['failure_reason'])) {
@@ -62,7 +62,7 @@ public function getResult(): string
/**
* @return FailureReasonResponse
*/
- public function getFailureReason(): FailureReasonResponse
+ public function getFailureReason(): ?FailureReasonResponse
{
return $this->failureReason;
}
diff --git a/src/DocScan/Session/Retrieve/PageResponse.php b/src/DocScan/Session/Retrieve/PageResponse.php
index 163b9ecf..eb3e6c45 100644
--- a/src/DocScan/Session/Retrieve/PageResponse.php
+++ b/src/DocScan/Session/Retrieve/PageResponse.php
@@ -21,6 +21,11 @@ class PageResponse
*/
private $frames = [];
+ /**
+ * @var string[]
+ */
+ private $extractionImageIds = [];
+
/**
* PageInfo constructor.
* @param array $page
@@ -38,6 +43,8 @@ public function __construct(array $page)
$this->frames[] = new FrameResponse($frame);
}
}
+
+ $this->extractionImageIds = $page['extraction_image_ids'] ?? [];
}
/**
@@ -63,4 +70,12 @@ public function getFrames(): array
{
return $this->frames;
}
+
+ /**
+ * @return string[]
+ */
+ public function getExtractionImageIds(): array
+ {
+ return $this->extractionImageIds;
+ }
}
diff --git a/src/DocScan/Session/Retrieve/ResourceContainer.php b/src/DocScan/Session/Retrieve/ResourceContainer.php
index 99b730d5..dd0d07c3 100644
--- a/src/DocScan/Session/Retrieve/ResourceContainer.php
+++ b/src/DocScan/Session/Retrieve/ResourceContainer.php
@@ -26,6 +26,16 @@ class ResourceContainer
*/
private $faceCapture = [];
+ /**
+ * @var ShareCodeResourceResponse[]
+ */
+ private $shareCodes = [];
+
+ /**
+ * @var ApplicantProfileResourceResponse[]
+ */
+ private $applicantProfiles = [];
+
/**
* ResourceContainer constructor.
* @param array $resources
@@ -47,6 +57,14 @@ public function __construct(array $resources)
if (isset($resources['face_capture'])) {
$this->faceCapture = $this->parseFaceCapture($resources['face_capture']);
}
+
+ if (isset($resources['share_codes'])) {
+ $this->shareCodes = $this->parseShareCodes($resources['share_codes']);
+ }
+
+ if (isset($resources['applicant_profiles'])) {
+ $this->applicantProfiles = $this->parseApplicantProfiles($resources['applicant_profiles']);
+ }
}
/**
@@ -161,6 +179,48 @@ public function getFaceCapture(): array
return $this->faceCapture;
}
+ /**
+ * @return ShareCodeResourceResponse[]
+ */
+ public function getShareCodes(): array
+ {
+ return $this->shareCodes;
+ }
+
+ /**
+ * @return ApplicantProfileResourceResponse[]
+ */
+ public function getApplicantProfiles(): array
+ {
+ return $this->applicantProfiles;
+ }
+
+ /**
+ * @param array> $shareCodes
+ * @return ShareCodeResourceResponse[]
+ */
+ private function parseShareCodes(array $shareCodes): array
+ {
+ $parsedShareCodes = [];
+ foreach ($shareCodes as $shareCode) {
+ $parsedShareCodes[] = new ShareCodeResourceResponse($shareCode);
+ }
+ return $parsedShareCodes;
+ }
+
+ /**
+ * @param array> $applicantProfiles
+ * @return ApplicantProfileResourceResponse[]
+ */
+ private function parseApplicantProfiles(array $applicantProfiles): array
+ {
+ $parsedApplicantProfiles = [];
+ foreach ($applicantProfiles as $applicantProfile) {
+ $parsedApplicantProfiles[] = new ApplicantProfileResourceResponse($applicantProfile);
+ }
+ return $parsedApplicantProfiles;
+ }
+
/**
* @param string $class
* @return mixed[]
diff --git a/src/DocScan/Session/Retrieve/ResourceResponse.php b/src/DocScan/Session/Retrieve/ResourceResponse.php
index aa7de1f3..973e0918 100644
--- a/src/DocScan/Session/Retrieve/ResourceResponse.php
+++ b/src/DocScan/Session/Retrieve/ResourceResponse.php
@@ -104,6 +104,8 @@ private function createTaskFromArray(array $task): TaskResponse
return new TextExtractionTaskResponse($task);
case Constants::SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION:
return new SupplementaryDocTextExtractionTaskResponse($task);
+ case Constants::VERIFY_SHARE_CODE_TASK:
+ return new VerifyShareCodeTaskResponse($task);
default:
return new TaskResponse($task);
}
diff --git a/src/DocScan/Session/Retrieve/ShareCodeMediaResponse.php b/src/DocScan/Session/Retrieve/ShareCodeMediaResponse.php
new file mode 100644
index 00000000..9ef99fc3
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/ShareCodeMediaResponse.php
@@ -0,0 +1,34 @@
+ $data
+ * @throws DateTimeException
+ */
+ public function __construct(array $data)
+ {
+ $this->media = isset($data['media'])
+ ? new MediaResponse($data['media'])
+ : null;
+ }
+
+ /**
+ * @return MediaResponse|null
+ */
+ public function getMedia(): ?MediaResponse
+ {
+ return $this->media;
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/ShareCodeResourceResponse.php b/src/DocScan/Session/Retrieve/ShareCodeResourceResponse.php
new file mode 100644
index 00000000..d7a80ad9
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/ShareCodeResourceResponse.php
@@ -0,0 +1,130 @@
+ $shareCode
+ *
+ * @throws DateTimeException
+ */
+ public function __construct(array $shareCode)
+ {
+ parent::__construct($shareCode);
+
+ $this->createdAt = isset($shareCode['created_at']) ?
+ DateTime::stringToDateTime($shareCode['created_at']) : null;
+ $this->lastUpdated = isset($shareCode['last_updated']) ?
+ DateTime::stringToDateTime($shareCode['last_updated']) : null;
+
+ $this->lookupProfile = isset($shareCode['lookup_profile'])
+ ? new ShareCodeMediaResponse($shareCode['lookup_profile'])
+ : null;
+
+ $this->returnedProfile = isset($shareCode['returned_profile'])
+ ? new ShareCodeMediaResponse($shareCode['returned_profile'])
+ : null;
+
+ $this->idPhoto = isset($shareCode['id_photo'])
+ ? new ShareCodeMediaResponse($shareCode['id_photo'])
+ : null;
+
+ $this->file = isset($shareCode['file'])
+ ? new ShareCodeMediaResponse($shareCode['file'])
+ : null;
+ }
+
+ /**
+ * @return \DateTime|null
+ */
+ public function getCreatedAt(): ?\DateTime
+ {
+ return $this->createdAt;
+ }
+
+ /**
+ * @return \DateTime|null
+ */
+ public function getLastUpdated(): ?\DateTime
+ {
+ return $this->lastUpdated;
+ }
+
+ /**
+ * @return ShareCodeMediaResponse|null
+ */
+ public function getLookupProfile(): ?ShareCodeMediaResponse
+ {
+ return $this->lookupProfile;
+ }
+
+ /**
+ * @return ShareCodeMediaResponse|null
+ */
+ public function getReturnedProfile(): ?ShareCodeMediaResponse
+ {
+ return $this->returnedProfile;
+ }
+
+ /**
+ * @return ShareCodeMediaResponse|null
+ */
+ public function getIdPhoto(): ?ShareCodeMediaResponse
+ {
+ return $this->idPhoto;
+ }
+
+ /**
+ * @return ShareCodeMediaResponse|null
+ */
+ public function getFile(): ?ShareCodeMediaResponse
+ {
+ return $this->file;
+ }
+
+ /**
+ * @return VerifyShareCodeTaskResponse[]
+ */
+ public function getVerifyShareCodeTasks(): array
+ {
+ return $this->filterTasksByType(VerifyShareCodeTaskResponse::class);
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/TaskRecommendationReasonResponse.php b/src/DocScan/Session/Retrieve/TaskRecommendationReasonResponse.php
new file mode 100644
index 00000000..c365b7cc
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/TaskRecommendationReasonResponse.php
@@ -0,0 +1,44 @@
+ $reason
+ */
+ public function __construct(array $reason)
+ {
+ $this->value = $reason['value'] ?? null;
+ $this->detail = $reason['detail'] ?? null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getValue(): ?string
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getDetail(): ?string
+ {
+ return $this->detail;
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/TaskRecommendationResponse.php b/src/DocScan/Session/Retrieve/TaskRecommendationResponse.php
new file mode 100644
index 00000000..9e4ffa72
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/TaskRecommendationResponse.php
@@ -0,0 +1,47 @@
+ $recommendation
+ */
+ public function __construct(array $recommendation)
+ {
+ $this->value = $recommendation['value'] ?? null;
+
+ if (isset($recommendation['reason'])) {
+ $this->reason = new TaskRecommendationReasonResponse($recommendation['reason']);
+ }
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getValue(): ?string
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return TaskRecommendationReasonResponse|null
+ */
+ public function getReason(): ?TaskRecommendationReasonResponse
+ {
+ return $this->reason;
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/TaskResponse.php b/src/DocScan/Session/Retrieve/TaskResponse.php
index e824cc51..324d8266 100644
--- a/src/DocScan/Session/Retrieve/TaskResponse.php
+++ b/src/DocScan/Session/Retrieve/TaskResponse.php
@@ -5,6 +5,7 @@
namespace Yoti\DocScan\Session\Retrieve;
use Yoti\DocScan\Constants;
+use Yoti\Exception\DateTimeException;
use Yoti\Util\DateTime;
class TaskResponse
@@ -44,9 +45,15 @@ class TaskResponse
*/
private $generatedMedia = [];
+ /**
+ * @var TaskRecommendationResponse|null
+ */
+ private $recommendation;
+
/**
* TaskResponse constructor.
* @param array $task
+ * @throws DateTimeException
*/
public function __construct(array $task)
{
@@ -67,6 +74,10 @@ public function __construct(array $task)
if (isset($task['generated_media'])) {
$this->generatedMedia = $this->parseGeneratedMedia($task['generated_media']);
}
+
+ if (isset($task['recommendation'])) {
+ $this->recommendation = new TaskRecommendationResponse($task['recommendation']);
+ }
}
/**
@@ -161,6 +172,14 @@ public function getGeneratedMedia(): array
return $this->generatedMedia;
}
+ /**
+ * @return TaskRecommendationResponse|null
+ */
+ public function getRecommendation(): ?TaskRecommendationResponse
+ {
+ return $this->recommendation;
+ }
+
/**
* @return GeneratedCheckResponse[]
*/
diff --git a/src/DocScan/Session/Retrieve/VerifyShareCodeTaskResponse.php b/src/DocScan/Session/Retrieve/VerifyShareCodeTaskResponse.php
new file mode 100644
index 00000000..52c629a6
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/VerifyShareCodeTaskResponse.php
@@ -0,0 +1,9 @@
+formatMessage($message, $response), 0, $previous);
diff --git a/src/Http/AuthStrategy/AuthStrategyInterface.php b/src/Http/AuthStrategy/AuthStrategyInterface.php
new file mode 100644
index 00000000..5fd1b173
--- /dev/null
+++ b/src/Http/AuthStrategy/AuthStrategyInterface.php
@@ -0,0 +1,34 @@
+ Headers to include in the request
+ */
+ public function createAuthHeaders(string $httpMethod, string $endpoint, ?Payload $payload = null): array;
+
+ /**
+ * Create query parameters required by this auth strategy.
+ *
+ * @return array Query parameters to include in the request
+ */
+ public function createQueryParams(): array;
+}
diff --git a/src/Http/AuthStrategy/BearerTokenStrategy.php b/src/Http/AuthStrategy/BearerTokenStrategy.php
new file mode 100644
index 00000000..2e651b82
--- /dev/null
+++ b/src/Http/AuthStrategy/BearerTokenStrategy.php
@@ -0,0 +1,51 @@
+authenticationToken = $authenticationToken;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createAuthHeaders(string $httpMethod, string $endpoint, ?Payload $payload = null): array
+ {
+ return [
+ 'Authorization' => 'Bearer ' . $this->authenticationToken,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createQueryParams(): array
+ {
+ return [];
+ }
+}
diff --git a/src/Http/AuthStrategy/NoAuthStrategy.php b/src/Http/AuthStrategy/NoAuthStrategy.php
new file mode 100644
index 00000000..181ce786
--- /dev/null
+++ b/src/Http/AuthStrategy/NoAuthStrategy.php
@@ -0,0 +1,33 @@
+pemFile = $pemFile;
+ $this->sdkId = $sdkId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createAuthHeaders(string $httpMethod, string $endpoint, ?Payload $payload = null): array
+ {
+ $digest = RequestSigner::sign(
+ $this->pemFile,
+ $endpoint,
+ $httpMethod,
+ $payload
+ );
+
+ return [
+ 'X-Yoti-Auth-Digest' => $digest,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createQueryParams(): array
+ {
+ $params = [
+ 'nonce' => self::generateNonce(),
+ 'timestamp' => (string)(round(microtime(true) * 1000)),
+ ];
+
+ if ($this->sdkId !== null) {
+ $params['sdkId'] = $this->sdkId;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Generate a UUID v4 nonce.
+ *
+ * @return string
+ */
+ private static function generateNonce(): string
+ {
+ return sprintf(
+ '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000,
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff)
+ );
+ }
+}
diff --git a/src/Http/Exception/NetworkException.php b/src/Http/Exception/NetworkException.php
index 0879d9f9..814871f7 100644
--- a/src/Http/Exception/NetworkException.php
+++ b/src/Http/Exception/NetworkException.php
@@ -19,7 +19,7 @@ class NetworkException extends ClientException implements NetworkExceptionInterf
public function __construct(
string $message,
RequestInterface $request,
- \Throwable $previous = null
+ ?\Throwable $previous = null
) {
$this->setRequest($request);
parent::__construct($message, 0, $previous);
diff --git a/src/Http/Exception/RequestException.php b/src/Http/Exception/RequestException.php
index 2ce34d5d..6c3a946c 100644
--- a/src/Http/Exception/RequestException.php
+++ b/src/Http/Exception/RequestException.php
@@ -19,7 +19,7 @@ class RequestException extends ClientException implements RequestExceptionInterf
public function __construct(
string $message,
RequestInterface $request,
- \Throwable $previous = null
+ ?\Throwable $previous = null
) {
$this->setRequest($request);
parent::__construct($message, 0, $previous);
diff --git a/src/Http/RequestBuilder.php b/src/Http/RequestBuilder.php
index 8afff19e..e1922228 100644
--- a/src/Http/RequestBuilder.php
+++ b/src/Http/RequestBuilder.php
@@ -8,6 +8,8 @@
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\StreamInterface;
+use Yoti\Http\AuthStrategy\AuthStrategyInterface;
+use Yoti\Http\AuthStrategy\SignedRequestStrategy;
use Yoti\Util\Config;
use Yoti\Util\PemFile;
@@ -32,6 +34,11 @@ class RequestBuilder
*/
private $pemFile;
+ /**
+ * @var AuthStrategyInterface|null
+ */
+ private $authStrategy;
+
/**
* @var array
*/
@@ -73,9 +80,9 @@ class RequestBuilder
private $multipartEntity;
/**
- * @param \Yoti\Util\Config $config
+ * @param \Yoti\Util\Config|null $config
*/
- public function __construct(Config $config = null)
+ public function __construct(?Config $config = null)
{
$this->config = $config ?? new Config();
}
@@ -135,6 +142,23 @@ public function withPemString(string $content): self
return $this->withPemFile(PemFile::fromString($content));
}
+ /**
+ * Set the authentication strategy for this request.
+ *
+ * When set, the auth strategy will be used instead of the default
+ * signed request behavior. If neither authStrategy nor pemFile is set,
+ * build() will throw an exception.
+ *
+ * @param AuthStrategyInterface $authStrategy
+ *
+ * @return \Yoti\Http\RequestBuilder
+ */
+ public function withAuthStrategy(AuthStrategyInterface $authStrategy): self
+ {
+ $this->authStrategy = $authStrategy;
+ return $this;
+ }
+
/**
* @param string $method
*
@@ -313,32 +337,6 @@ private function validateMethod(): void
}
}
- /**
- * @return string
- */
- private static function generateNonce(): string
- {
- return sprintf(
- '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
- // 32 bits for "time_low"
- mt_rand(0, 0xffff),
- mt_rand(0, 0xffff),
- // 16 bits for "time_mid"
- mt_rand(0, 0xffff),
- // 16 bits for "time_hi_and_version",
- // four most significant bits holds version number 4
- mt_rand(0, 0x0fff) | 0x4000,
- // 16 bits, 8 bits for "clk_seq_hi_res",
- // 8 bits for "clk_seq_low",
- // two most significant bits holds zero and one for variant DCE1.1
- mt_rand(0, 0x3fff) | 0x8000,
- // 48 bits for "node"
- mt_rand(0, 0xffff),
- mt_rand(0, 0xffff),
- mt_rand(0, 0xffff)
- );
- }
-
/**
* @return \Yoti\Http\Request
*
@@ -350,31 +348,38 @@ public function build(): Request
throw new \InvalidArgumentException('Base URL must be provided to ' . __CLASS__);
}
- if (!isset($this->pemFile)) {
- throw new \InvalidArgumentException('Pem file must be provided to ' . __CLASS__);
- }
-
$this->validateMethod();
- // Add nonce and timestamp to the URL.
- $this
- ->withQueryParam('nonce', self::generateNonce())
- ->withQueryParam('timestamp', (string)(round(microtime(true) * 1000)));
+ // Resolve the auth strategy:
+ // 1. Explicit authStrategy takes priority
+ // 2. PemFile present: use legacy SignedRequestStrategy (backward compatible)
+ // 3. Neither: throw
+ $authStrategy = $this->resolveAuthStrategy();
- $endpointWithParams = $this->endpoint . '?' . http_build_query($this->queryParams);
+ // Merge strategy query params with manually set query params.
+ // Manual params go first to preserve backward-compatible URL ordering.
+ $strategyQueryParams = $authStrategy->createQueryParams();
+ $allQueryParams = array_merge($this->queryParams, $strategyQueryParams);
+
+ $queryString = http_build_query($allQueryParams);
+ $endpointWithParams = $queryString !== '' ? $this->endpoint . '?' . $queryString : $this->endpoint;
$payload = isset($this->multipartEntity) ? Payload::fromStream($this->multipartEntity->createStream()) :
$this->payload;
- $this->withHeader(self::YOTI_DIGEST_HEADER_KEY, RequestSigner::sign(
- $this->pemFile,
- $endpointWithParams,
+ // Get auth headers from strategy.
+ $authHeaders = $authStrategy->createAuthHeaders(
$this->method,
+ $endpointWithParams,
$payload
- ));
+ );
- $url = $this->baseUrl . $endpointWithParams;
+ // Merge auth headers into manual headers.
+ foreach ($authHeaders as $name => $value) {
+ $this->withHeader($name, $value);
+ }
+ $url = $this->baseUrl . $endpointWithParams;
$message = new RequestMessage(
$this->method,
@@ -386,6 +391,28 @@ public function build(): Request
return new Request($message, $this->client ?? $this->config->getHttpClient());
}
+ /**
+ * Resolve the authentication strategy to use.
+ *
+ * @return AuthStrategyInterface
+ *
+ * @throws \InvalidArgumentException
+ */
+ private function resolveAuthStrategy(): AuthStrategyInterface
+ {
+ if (isset($this->authStrategy)) {
+ return $this->authStrategy;
+ }
+
+ if (isset($this->pemFile)) {
+ return new SignedRequestStrategy($this->pemFile);
+ }
+
+ throw new \InvalidArgumentException(
+ 'Either an AuthStrategy or a PEM file must be provided to ' . __CLASS__
+ );
+ }
+
/**
* @return StreamInterface|null
*/
diff --git a/src/Http/RequestSigner.php b/src/Http/RequestSigner.php
index 99bdff79..abf09fe0 100644
--- a/src/Http/RequestSigner.php
+++ b/src/Http/RequestSigner.php
@@ -26,7 +26,7 @@ public static function sign(
PemFile $pemFile,
string $endpoint,
string $httpMethod,
- Payload $payload = null
+ ?Payload $payload = null
): string {
$messageToSign = "{$httpMethod}&$endpoint";
if ($payload instanceof Payload) {
diff --git a/src/Identity/Content/ApplicationContent.php b/src/Identity/Content/ApplicationContent.php
index 8487fc4a..7c0d267f 100644
--- a/src/Identity/Content/ApplicationContent.php
+++ b/src/Identity/Content/ApplicationContent.php
@@ -10,7 +10,7 @@ class ApplicationContent
private ?ApplicationProfile $profile;
private ?ExtraData $extraData;
- public function __construct(ApplicationProfile $profile = null, ExtraData $extraData = null)
+ public function __construct(?ApplicationProfile $profile = null, ?ExtraData $extraData = null)
{
$this->profile = $profile;
$this->extraData = $extraData;
diff --git a/src/Identity/Content/Content.php b/src/Identity/Content/Content.php
index 9cf61c01..cef3ef37 100644
--- a/src/Identity/Content/Content.php
+++ b/src/Identity/Content/Content.php
@@ -9,7 +9,7 @@ class Content
private ?string $profile;
private ?string $extraData;
- public function __construct(string $profile = null, string $extraData = null)
+ public function __construct(?string $profile = null, ?string $extraData = null)
{
$this->profile = $profile;
$this->extraData = $extraData;
diff --git a/src/Identity/Content/UserContent.php b/src/Identity/Content/UserContent.php
index a32c2dfd..a69e24fd 100644
--- a/src/Identity/Content/UserContent.php
+++ b/src/Identity/Content/UserContent.php
@@ -10,7 +10,7 @@ class UserContent
private ?UserProfile $profile;
private ?ExtraData $extraData;
- public function __construct(UserProfile $profile = null, ExtraData $extraData = null)
+ public function __construct(?UserProfile $profile = null, ?ExtraData $extraData = null)
{
$this->profile = $profile;
$this->extraData = $extraData;
diff --git a/src/Identity/DigitalIdentityService.php b/src/Identity/DigitalIdentityService.php
index 344b18c7..0b68ffe6 100644
--- a/src/Identity/DigitalIdentityService.php
+++ b/src/Identity/DigitalIdentityService.php
@@ -4,6 +4,7 @@
use Yoti\Constants;
use Yoti\Exception\DigitalIdentityException;
+use Yoti\Http\AuthStrategy\AuthStrategyInterface;
use Yoti\Http\Payload;
use Yoti\Http\RequestBuilder;
use Yoti\Util\Config;
@@ -19,11 +20,25 @@ class DigitalIdentityService
private const IDENTITY_SESSION_RECEIPT_RETRIEVAL = '/v2/receipts/%s';
private const IDENTITY_SESSION_RECEIPT_KEY_RETRIEVAL = '/v2/wrapped-item-keys/%s';
- private string $sdkId;
+ /**
+ * @var string
+ */
+ private $sdkId;
+
+ /**
+ * @var PemFile|null
+ */
+ private $pemFile;
- private PemFile $pemFile;
+ /**
+ * @var AuthStrategyInterface|null
+ */
+ private $authStrategy;
- private Config $config;
+ /**
+ * @var Config
+ */
+ private $config;
public function __construct(string $sdkId, PemFile $pemFile, Config $config)
{
@@ -32,15 +47,62 @@ public function __construct(string $sdkId, PemFile $pemFile, Config $config)
$this->config = $config;
}
+ /**
+ * Create a DigitalIdentityService instance using an authentication strategy.
+ *
+ * When using BearerTokenStrategy (central auth), no sdkId or PEM
+ * is required since the Bearer token handles authorization.
+ *
+ * @param AuthStrategyInterface $authStrategy
+ * @param Config $config
+ *
+ * @return self
+ */
+ public static function withAuthStrategy(AuthStrategyInterface $authStrategy, Config $config): self
+ {
+ $instance = new \ReflectionClass(self::class);
+ $service = $instance->newInstanceWithoutConstructor();
+ $service->authStrategy = $authStrategy;
+ $service->config = $config;
+ $service->sdkId = '';
+ return $service;
+ }
+
+ /**
+ * Apply authentication to a RequestBuilder.
+ *
+ * If an explicit auth strategy was set, uses it directly.
+ * Otherwise falls back to the legacy PemFile + X-Yoti-Auth-Id header approach.
+ *
+ * @param RequestBuilder $builder
+ * @param bool $includeAuthId Whether to include X-Yoti-Auth-Id header (legacy mode only)
+ *
+ * @return RequestBuilder
+ */
+ private function applyAuth(RequestBuilder $builder, bool $includeAuthId = true): RequestBuilder
+ {
+ if ($this->authStrategy !== null) {
+ return $builder->withAuthStrategy($this->authStrategy);
+ }
+
+ if ($this->pemFile !== null) {
+ $builder->withPemFile($this->pemFile);
+ }
+ if ($includeAuthId && $this->sdkId !== null && $this->sdkId !== '') {
+ $builder->withHeader('X-Yoti-Auth-Id', $this->sdkId);
+ }
+ return $builder;
+ }
+
public function createShareSession(ShareSessionRequest $shareSessionRequest): ShareSessionCreated
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(self::IDENTITY_SESSION_CREATION)
- ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
->withPost()
- ->withPayload(Payload::fromJsonData($shareSessionRequest))
- ->withPemFile($this->pemFile)
+ ->withPayload(Payload::fromJsonData($shareSessionRequest));
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -54,12 +116,12 @@ public function createShareSession(ShareSessionRequest $shareSessionRequest): Sh
public function createShareQrCode(string $sessionId): ShareSessionCreatedQrCode
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_QR_CODE_CREATION, $sessionId))
- ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
- ->withPost()
- ->withPemFile($this->pemFile)
+ ->withPost();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -73,12 +135,12 @@ public function createShareQrCode(string $sessionId): ShareSessionCreatedQrCode
public function fetchShareQrCode(string $qrCodeId): ShareSessionFetchedQrCode
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_QR_CODE_RETRIEVAL, $qrCodeId))
- ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
- ->withGet()
- ->withPemFile($this->pemFile)
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -92,12 +154,12 @@ public function fetchShareQrCode(string $qrCodeId): ShareSessionFetchedQrCode
public function fetchShareSession(string $sessionId): ShareSessionFetched
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_RETRIEVAL, $sessionId))
- ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
- ->withGet()
- ->withPemFile($this->pemFile)
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -118,6 +180,13 @@ public function fetchShareReceipt(string $receiptId): Receipt
$wrappedReceipt = $this->doFetchShareReceipt($receiptId);
if (null === $wrappedReceipt->getError()) {
+ if ($this->pemFile === null) {
+ throw new DigitalIdentityException(
+ 'Cannot decrypt receipt without a PEM file. '
+ . 'Receipt decryption is not supported in token-auth mode.'
+ );
+ }
+
$receiptKey = $this->fetchShareReceiptKey($wrappedReceipt);
return $receiptParser->createSuccess($wrappedReceipt, $receiptKey, $this->pemFile);
@@ -129,12 +198,12 @@ public function fetchShareReceipt(string $receiptId): Receipt
private function doFetchShareReceipt(string $receiptId): WrappedReceipt
{
$receiptIdUrl = strtr($receiptId, '+/', '-_');
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_RECEIPT_RETRIEVAL, $receiptIdUrl))
- ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
- ->withGet()
- ->withPemFile($this->pemFile)
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
@@ -148,15 +217,15 @@ private function doFetchShareReceipt(string $receiptId): WrappedReceipt
private function fetchShareReceiptKey(WrappedReceipt $wrappedReceipt): ReceiptItemKey
{
- $response = (new RequestBuilder($this->config))
+ $builder = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(
self::IDENTITY_SESSION_RECEIPT_KEY_RETRIEVAL,
$wrappedReceipt->getWrappedItemKeyId()
))
- ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
- ->withGet()
- ->withPemFile($this->pemFile)
+ ->withGet();
+
+ $response = $this->applyAuth($builder)
->build()
->execute();
diff --git a/src/Identity/Policy/Policy.php b/src/Identity/Policy/Policy.php
index a71e1fdc..5c54f2fa 100644
--- a/src/Identity/Policy/Policy.php
+++ b/src/Identity/Policy/Policy.php
@@ -37,7 +37,7 @@ class Policy implements \JsonSerializable
* @param int[] $wantedAuthTypes
* Auth types represents the authentication type to be used.
* @param object $identityProfileRequirements
- * @param object $advancedidentityProfileRequirements
+ * @param object $advancedIdentityProfileRequirements
*/
public function __construct(
array $wantedAttributes,
diff --git a/src/Identity/Policy/PolicyBuilder.php b/src/Identity/Policy/PolicyBuilder.php
index a3b8f479..8ad89aaa 100644
--- a/src/Identity/Policy/PolicyBuilder.php
+++ b/src/Identity/Policy/PolicyBuilder.php
@@ -51,8 +51,8 @@ public function withWantedAttribute(WantedAttribute $wantedAttribute): self
*/
public function withWantedAttributeByName(
string $name,
- array $constraints = null,
- bool $acceptSelfAsserted = null
+ ?array $constraints = null,
+ ?bool $acceptSelfAsserted = null
): self {
$wantedAttributeBuilder = (new WantedAttributeBuilder())
->withName($name);
@@ -71,7 +71,7 @@ public function withWantedAttributeByName(
/**
* @param Constraint[]|null $constraints
*/
- public function withFamilyName(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withFamilyName(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_FAMILY_NAME,
@@ -83,7 +83,7 @@ public function withFamilyName(array $constraints = null, bool $acceptSelfAssert
/**
* @param Constraint[]|null $constraints
*/
- public function withGivenNames(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withGivenNames(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_GIVEN_NAMES,
@@ -95,7 +95,7 @@ public function withGivenNames(array $constraints = null, bool $acceptSelfAssert
/**
* @param Constraint[]|null $constraints
*/
- public function withFullName(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withFullName(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_FULL_NAME,
@@ -107,7 +107,7 @@ public function withFullName(array $constraints = null, bool $acceptSelfAsserted
/**
* @param Constraint[]|null $constraints
*/
- public function withDateOfBirth(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withDateOfBirth(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_DATE_OF_BIRTH,
@@ -119,7 +119,7 @@ public function withDateOfBirth(array $constraints = null, bool $acceptSelfAsser
/**
* @param Constraint[]|null $constraints
*/
- public function withAgeOver(int $age, array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withAgeOver(int $age, ?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withAgeDerivedAttribute(
UserProfile::AGE_OVER . $age,
@@ -131,7 +131,7 @@ public function withAgeOver(int $age, array $constraints = null, bool $acceptSel
/**
* @param Constraint[]|null $constraints
*/
- public function withAgeUnder(int $age, array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withAgeUnder(int $age, ?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withAgeDerivedAttribute(
UserProfile::AGE_UNDER . $age,
@@ -145,8 +145,8 @@ public function withAgeUnder(int $age, array $constraints = null, bool $acceptSe
*/
public function withAgeDerivedAttribute(
string $derivation,
- array $constraints = null,
- bool $acceptSelfAsserted = null
+ ?array $constraints = null,
+ ?bool $acceptSelfAsserted = null
): self {
$wantedAttributeBuilder = (new WantedAttributeBuilder())
->withName(UserProfile::ATTR_DATE_OF_BIRTH)
@@ -166,7 +166,7 @@ public function withAgeDerivedAttribute(
/**
* @param Constraint[]|null $constraints
*/
- public function withGender(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withGender(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_GENDER,
@@ -178,7 +178,7 @@ public function withGender(array $constraints = null, bool $acceptSelfAsserted =
/**
* @param Constraint[]|null $constraints
*/
- public function withPostalAddress(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withPostalAddress(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_POSTAL_ADDRESS,
@@ -190,7 +190,7 @@ public function withPostalAddress(array $constraints = null, bool $acceptSelfAss
/**
* @param Constraint[]|null $constraints
*/
- public function withStructuredPostalAddress(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withStructuredPostalAddress(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_STRUCTURED_POSTAL_ADDRESS,
@@ -202,7 +202,7 @@ public function withStructuredPostalAddress(array $constraints = null, bool $acc
/**
* @param Constraint[]|null $constraints
*/
- public function withNationality(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withNationality(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_NATIONALITY,
@@ -214,7 +214,7 @@ public function withNationality(array $constraints = null, bool $acceptSelfAsser
/**
* @param Constraint[]|null $constraints
*/
- public function withPhoneNumber(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withPhoneNumber(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_PHONE_NUMBER,
@@ -226,7 +226,7 @@ public function withPhoneNumber(array $constraints = null, bool $acceptSelfAsser
/**
* @param Constraint[]|null $constraints
*/
- public function withSelfie(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withSelfie(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_SELFIE,
@@ -238,7 +238,7 @@ public function withSelfie(array $constraints = null, bool $acceptSelfAsserted =
/**
* @param Constraint[]|null $constraints
*/
- public function withDocumentDetails(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withDocumentDetails(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_DOCUMENT_DETAILS,
@@ -250,7 +250,7 @@ public function withDocumentDetails(array $constraints = null, bool $acceptSelfA
/**
* @param Constraint[]|null $constraints
*/
- public function withDocumentImages(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withDocumentImages(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_DOCUMENT_IMAGES,
@@ -262,7 +262,7 @@ public function withDocumentImages(array $constraints = null, bool $acceptSelfAs
/**
* @param Constraint[]|null $constraints
*/
- public function withEmail(array $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withEmail(?array $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_EMAIL_ADDRESS,
diff --git a/src/Identity/Policy/WantedAttribute.php b/src/Identity/Policy/WantedAttribute.php
index dc34da79..47005af1 100644
--- a/src/Identity/Policy/WantedAttribute.php
+++ b/src/Identity/Policy/WantedAttribute.php
@@ -29,10 +29,10 @@ class WantedAttribute implements \JsonSerializable
*/
public function __construct(
string $name,
- string $derivation = null,
+ ?string $derivation = null,
bool $optional = false,
- bool $acceptSelfAsserted = null,
- array $constraints = null
+ ?bool $acceptSelfAsserted = null,
+ ?array $constraints = null
) {
Validation::notEmptyString($name, 'name');
$this->name = $name;
diff --git a/src/Identity/Receipt.php b/src/Identity/Receipt.php
index 162761f2..893c5459 100644
--- a/src/Identity/Receipt.php
+++ b/src/Identity/Receipt.php
@@ -12,8 +12,8 @@ class Receipt
private string $id;
private string $sessionId;
private \DateTime $timestamp;
- private ApplicationContent $applicationContent;
- private UserContent $userContent;
+ private ?ApplicationContent $applicationContent;
+ private ?UserContent $userContent;
private ?string $rememberMeId;
private ?string $parentRememberMeId;
private ?string $error;
@@ -23,8 +23,8 @@ public function __construct(
string $id,
string $sessionId,
\DateTime $timestamp,
- ApplicationContent $applicationContent,
- UserContent $userContent,
+ ?ApplicationContent $applicationContent,
+ ?UserContent $userContent,
?string $rememberMeId,
?string $parentRememberMeId,
?string $error,
@@ -58,20 +58,28 @@ public function getTimestamp(): \DateTime
public function getProfile(): ?UserProfile
{
- return $this->userContent->getProfile();
+ if ($this->userContent !== null) {
+ return $this->userContent->getProfile();
+ } else {
+ return null;
+ }
}
public function getExtraData(): ?ExtraData
{
- return $this->userContent->getExtraData();
+ if ($this->userContent !== null) {
+ return $this->userContent->getExtraData();
+ } else {
+ return null;
+ }
}
- public function getApplicationContent(): ApplicationContent
+ public function getApplicationContent(): ?ApplicationContent
{
return $this->applicationContent;
}
- public function getUserContent(): UserContent
+ public function getUserContent(): ?UserContent
{
return $this->userContent;
}
diff --git a/src/Identity/ReceiptBuilder.php b/src/Identity/ReceiptBuilder.php
index 7a091388..1ec378f2 100644
--- a/src/Identity/ReceiptBuilder.php
+++ b/src/Identity/ReceiptBuilder.php
@@ -16,9 +16,9 @@ class ReceiptBuilder
private \DateTime $timestamp;
- private ApplicationContent $applicationContent;
+ private ?ApplicationContent $applicationContent = null;
- private UserContent $userContent;
+ private ?UserContent $userContent = null;
private ?string $rememberMeId = null;
@@ -42,14 +42,14 @@ public function withSessionId(string $sessionId): self
return $this;
}
- public function withRememberMeId(string $rememberMeId = null): self
+ public function withRememberMeId(?string $rememberMeId = null): self
{
$this->rememberMeId = $rememberMeId;
return $this;
}
- public function withParentRememberMeId(string $parentRememberMeId = null): self
+ public function withParentRememberMeId(?string $parentRememberMeId = null): self
{
$this->parentRememberMeId = $parentRememberMeId;
@@ -63,28 +63,28 @@ public function withTimestamp(\DateTime $timestamp): self
return $this;
}
- public function withApplicationContent(ApplicationProfile $profile, ExtraData $extraData = null): self
+ public function withApplicationContent(ApplicationProfile $profile, ?ExtraData $extraData = null): self
{
$this->applicationContent = new ApplicationContent($profile, $extraData);
return $this;
}
- public function withUserContent(UserProfile $profile = null, ExtraData $extraData = null): self
+ public function withUserContent(?UserProfile $profile = null, ?ExtraData $extraData = null): self
{
$this->userContent = new UserContent($profile, $extraData);
return $this;
}
- public function withError(string $error = null): self
+ public function withError(?string $error = null): self
{
$this->error = $error;
return $this;
}
- public function withErrorReason(ErrorReason $errorReason = null): self
+ public function withErrorReason(?ErrorReason $errorReason = null): self
{
$this->errorReason = $errorReason;
diff --git a/src/Identity/ReceiptParser.php b/src/Identity/ReceiptParser.php
index 375249da..39fae9ee 100644
--- a/src/Identity/ReceiptParser.php
+++ b/src/Identity/ReceiptParser.php
@@ -21,7 +21,7 @@ class ReceiptParser
*/
private $logger;
- public function __construct(LoggerInterface $logger = null)
+ public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger ?? new Logger();
}
@@ -93,8 +93,11 @@ public function createFailure(WrappedReceipt $wrappedReceipt): Receipt
->build();
}
- private function decryptReceiptKey(string $wrappedKey, ReceiptItemKey $wrappedItemKey, PemFile $pemFile): string
+ private function decryptReceiptKey(?string $wrappedKey, ReceiptItemKey $wrappedItemKey, PemFile $pemFile): string
{
+ if ($wrappedKey == null) {
+ throw new EncryptedDataException('Wrapped is null');
+ }
// Convert 'iv' and 'value' from base64 to binary
$iv = (string)base64_decode($wrappedItemKey->getIv(), true);
$encryptedItemKey = (string)base64_decode($wrappedItemKey->getValue(), true);
diff --git a/src/Identity/WrappedReceipt.php b/src/Identity/WrappedReceipt.php
index 7d1f4b74..28e93f60 100644
--- a/src/Identity/WrappedReceipt.php
+++ b/src/Identity/WrappedReceipt.php
@@ -19,9 +19,9 @@ class WrappedReceipt
private Content $otherPartyContent;
- private string $wrappedItemKeyId;
+ private ?string $wrappedItemKeyId = null;
- private string $wrappedKey;
+ private ?string $wrappedKey;
private ?string $rememberMeId = null;
@@ -38,8 +38,8 @@ public function __construct(array $sessionData)
$this->id = $sessionData['id'];
$this->sessionId = $sessionData['sessionId'];
$this->timestamp = DateTime::stringToDateTime($sessionData['timestamp']);
- $this->wrappedItemKeyId = $sessionData['wrappedItemKeyId'];
- $this->wrappedKey = $sessionData['wrappedKey'];
+ $this->wrappedItemKeyId = $sessionData['wrappedItemKeyId'] ?? null;
+ $this->wrappedKey = $sessionData['wrappedKey'] ?? null;
if (isset($sessionData['content'])) {
$this->content = new Content(
@@ -63,10 +63,10 @@ public function __construct(array $sessionData)
if (isset($sessionData['error'])) {
$this->error = $sessionData['error'];
}
- if (isset($sessionData['errorDetails'])) {
- if (isset($sessionData["error_details"]["error_reason"]["requirements_not_met_details"])) {
+ if (isset($sessionData['errorReason'])) {
+ if (isset($sessionData["errorReason"]["requirements_not_met_details"])) {
$this->errorReason = new ErrorReason(
- $sessionData['errorDetails']['error_reason']['requirements_not_met_details']
+ $sessionData["errorReason"]["requirements_not_met_details"]
);
}
}
@@ -115,12 +115,12 @@ public function getOtherPartyExtraData(): ?string
return $this->otherPartyContent->getExtraData();
}
- public function getWrappedItemKeyId(): string
+ public function getWrappedItemKeyId(): ?string
{
return $this->wrappedItemKeyId;
}
- public function getWrappedKey(): string
+ public function getWrappedKey(): ?string
{
return $this->wrappedKey;
}
diff --git a/src/Profile/Attribute.php b/src/Profile/Attribute.php
index 1117f728..39f1d8b2 100644
--- a/src/Profile/Attribute.php
+++ b/src/Profile/Attribute.php
@@ -36,7 +36,7 @@ class Attribute
* @param Anchor[] $anchors
* @param string|null $id
*/
- public function __construct(string $name, $value, array $anchors, string $id = null)
+ public function __construct(string $name, $value, array $anchors, ?string $id = null)
{
$this->name = $name;
$this->value = $value;
diff --git a/src/Profile/ExtraData/AttributeIssuanceDetails.php b/src/Profile/ExtraData/AttributeIssuanceDetails.php
index 019350c6..31d6e9ca 100644
--- a/src/Profile/ExtraData/AttributeIssuanceDetails.php
+++ b/src/Profile/ExtraData/AttributeIssuanceDetails.php
@@ -25,10 +25,10 @@ class AttributeIssuanceDetails
/**
* @param string $token
- * @param \DateTime $expiryDate
+ * @param \DateTime|null $expiryDate
* @param \Yoti\Profile\ExtraData\AttributeDefinition[] $issuingAttributes
*/
- public function __construct(string $token, \DateTime $expiryDate = null, array $issuingAttributes = [])
+ public function __construct(string $token, ?\DateTime $expiryDate = null, array $issuingAttributes = [])
{
$this->token = $token;
diff --git a/src/Profile/UserProfile.php b/src/Profile/UserProfile.php
index d9ac0210..e4189a56 100644
--- a/src/Profile/UserProfile.php
+++ b/src/Profile/UserProfile.php
@@ -28,7 +28,6 @@ class UserProfile extends BaseProfile
public const ATTR_DOCUMENT_IMAGES = 'document_images';
public const ATTR_STRUCTURED_POSTAL_ADDRESS = 'structured_postal_address';
public const ATTR_IDENTITY_PROFILE_REPORT = 'identity_profile_report';
-
/** @var \Yoti\Profile\Attribute\AgeVerification[] */
private $ageVerifications;
diff --git a/src/Profile/Util/Attribute/AnchorConverter.php b/src/Profile/Util/Attribute/AnchorConverter.php
index a13d6147..93f9b130 100644
--- a/src/Profile/Util/Attribute/AnchorConverter.php
+++ b/src/Profile/Util/Attribute/AnchorConverter.php
@@ -60,8 +60,31 @@ private static function decodeAnchorValue(string $extEncodedValue): string
{
$encodedBER = ASN1::extractBER($extEncodedValue);
$decodedValArr = ASN1::decodeBER($encodedBER);
+
if (isset($decodedValArr[0]['content'][0]['content'])) {
- return $decodedValArr[0]['content'][0]['content'];
+ $value = $decodedValArr[0]['content'][0]['content'];
+
+ if (!is_string($value)) {
+ return '';
+ }
+
+ $detectionOrder = mb_detect_order();
+ $encoding = mb_detect_encoding($value, is_array($detectionOrder) ? $detectionOrder : null, true);
+
+ if (is_string($encoding)) {
+ if ($encoding !== 'UTF-8') {
+ // PHPStan implies $value is string, $encoding is valid string, so result is string.
+ return mb_convert_encoding($value, 'UTF-8', $encoding);
+ }
+ // It is UTF-8
+ return $value;
+ } else { // $encoding is false (detection failed)
+ if (!mb_check_encoding($value, 'UTF-8')) {
+ // PHPStan implies $value is string, so result is string.
+ return mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1');
+ }
+ return $value; // It's valid UTF-8 despite detection failing
+ }
}
return '';
}
@@ -108,16 +131,28 @@ private static function convertCertToX509(string $certificate): \stdClass
$X509 = new X509();
$X509Data = $X509->loadX509($certificate);
- /** We need because of new 3.0 version phpseclib @link https://github.com/phpseclib/phpseclib/issues/1738 */
array_walk_recursive($X509Data, function (&$item): void {
- if (is_string($item) && mb_detect_encoding($item) != 'ASCII') {
- $item = base64_encode($item);
+ if (is_string($item)) {
+ $detectionOrder = mb_detect_order();
+ $encoding = mb_detect_encoding($item, is_array($detectionOrder) ? $detectionOrder : null, true);
+
+ if (is_string($encoding)) {
+ if ($encoding !== 'UTF-8' && $encoding !== 'ASCII') {
+ // PHPStan implies $item is string, $encoding is valid string, so result is string.
+ // The 'else' branch for base64_encode was deemed unreachable by PHPStan.
+ $item = mb_convert_encoding($item, 'UTF-8', $encoding);
+ }
+ // If $encoding is 'UTF-8' or 'ASCII', $item is left as is.
+ } else { // $encoding is false (detection failed)
+ if (!mb_check_encoding($item, 'UTF-8') && !mb_check_encoding($item, 'ASCII')) {
+ $item = base64_encode($item);
+ }
+ // If it's valid UTF-8/ASCII despite detection failing, $item is left as is.
+ }
}
});
$decodedX509Data = Json::decode(Json::encode(Json::convertFromLatin1ToUtf8Recursively($X509Data)), false);
- // Ensure serial number is cast to string.
- // @see \phpseclib\Math\BigInteger::__toString()
$decodedX509Data
->tbsCertificate
->serialNumber
diff --git a/src/ShareUrl/Policy/DynamicPolicyBuilder.php b/src/ShareUrl/Policy/DynamicPolicyBuilder.php
index 4d3ba083..f7a1164b 100644
--- a/src/ShareUrl/Policy/DynamicPolicyBuilder.php
+++ b/src/ShareUrl/Policy/DynamicPolicyBuilder.php
@@ -77,8 +77,8 @@ public function withWantedAttribute(WantedAttribute $wantedAttribute): self
*/
public function withWantedAttributeByName(
string $name,
- Constraints $constraints = null,
- bool $acceptSelfAsserted = null
+ ?Constraints $constraints = null,
+ ?bool $acceptSelfAsserted = null
): self {
$wantedAttributeBuilder = (new WantedAttributeBuilder())
->withName($name);
@@ -100,7 +100,7 @@ public function withWantedAttributeByName(
*
* @return $this
*/
- public function withFamilyName(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withFamilyName(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_FAMILY_NAME,
@@ -115,7 +115,7 @@ public function withFamilyName(Constraints $constraints = null, bool $acceptSelf
*
* @return self
*/
- public function withGivenNames(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withGivenNames(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_GIVEN_NAMES,
@@ -130,7 +130,7 @@ public function withGivenNames(Constraints $constraints = null, bool $acceptSelf
*
* @return self
*/
- public function withFullName(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withFullName(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_FULL_NAME,
@@ -145,7 +145,7 @@ public function withFullName(Constraints $constraints = null, bool $acceptSelfAs
*
* @return $this
*/
- public function withDateOfBirth(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withDateOfBirth(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_DATE_OF_BIRTH,
@@ -161,7 +161,7 @@ public function withDateOfBirth(Constraints $constraints = null, bool $acceptSel
*
* @return $this
*/
- public function withAgeOver(int $age, Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withAgeOver(int $age, ?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withAgeDerivedAttribute(
UserProfile::AGE_OVER . (string) $age,
@@ -177,7 +177,7 @@ public function withAgeOver(int $age, Constraints $constraints = null, bool $acc
*
* @return $this
*/
- public function withAgeUnder(int $age, Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withAgeUnder(int $age, ?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withAgeDerivedAttribute(
UserProfile::AGE_UNDER . (string) $age,
@@ -195,8 +195,8 @@ public function withAgeUnder(int $age, Constraints $constraints = null, bool $ac
*/
public function withAgeDerivedAttribute(
string $derivation,
- Constraints $constraints = null,
- bool $acceptSelfAsserted = null
+ ?Constraints $constraints = null,
+ ?bool $acceptSelfAsserted = null
): self {
$wantedAttributeBuilder = (new WantedAttributeBuilder())
->withName(UserProfile::ATTR_DATE_OF_BIRTH)
@@ -216,7 +216,7 @@ public function withAgeDerivedAttribute(
*
* @return $this
*/
- public function withGender(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withGender(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_GENDER,
@@ -231,7 +231,7 @@ public function withGender(Constraints $constraints = null, bool $acceptSelfAsse
*
* @return $this
*/
- public function withPostalAddress(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withPostalAddress(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_POSTAL_ADDRESS,
@@ -246,8 +246,10 @@ public function withPostalAddress(Constraints $constraints = null, bool $acceptS
*
* @return $this
*/
- public function withStructuredPostalAddress(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
- {
+ public function withStructuredPostalAddress(
+ ?Constraints $constraints = null,
+ ?bool $acceptSelfAsserted = null
+ ): self {
return $this->withWantedAttributeByName(
UserProfile::ATTR_STRUCTURED_POSTAL_ADDRESS,
$constraints,
@@ -261,7 +263,7 @@ public function withStructuredPostalAddress(Constraints $constraints = null, boo
*
* @return $this
*/
- public function withNationality(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withNationality(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_NATIONALITY,
@@ -276,7 +278,7 @@ public function withNationality(Constraints $constraints = null, bool $acceptSel
*
* @return $this
*/
- public function withPhoneNumber(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withPhoneNumber(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_PHONE_NUMBER,
@@ -291,7 +293,7 @@ public function withPhoneNumber(Constraints $constraints = null, bool $acceptSel
*
* @return $this
*/
- public function withSelfie(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withSelfie(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_SELFIE,
@@ -306,7 +308,7 @@ public function withSelfie(Constraints $constraints = null, bool $acceptSelfAsse
*
* @return $this
*/
- public function withDocumentDetails(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withDocumentDetails(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_DOCUMENT_DETAILS,
@@ -321,7 +323,7 @@ public function withDocumentDetails(Constraints $constraints = null, bool $accep
*
* @return $this
*/
- public function withDocumentImages(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withDocumentImages(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_DOCUMENT_IMAGES,
@@ -336,7 +338,7 @@ public function withDocumentImages(Constraints $constraints = null, bool $accept
*
* @return $this
*/
- public function withEmail(Constraints $constraints = null, bool $acceptSelfAsserted = null): self
+ public function withEmail(?Constraints $constraints = null, ?bool $acceptSelfAsserted = null): self
{
return $this->withWantedAttributeByName(
UserProfile::ATTR_EMAIL_ADDRESS,
@@ -406,7 +408,7 @@ public function withIdentityProfileRequirements($identityProfileRequirements): s
}
/**
- * Use an Identity Profile Requirement object for the share
+ * Use an Advanced Identity Profile Requirement object for the share
*
* @param object $advancedIdentityProfileRequirements
* @return $this
diff --git a/src/ShareUrl/Policy/WantedAttribute.php b/src/ShareUrl/Policy/WantedAttribute.php
index a5b0f2ed..d176070a 100644
--- a/src/ShareUrl/Policy/WantedAttribute.php
+++ b/src/ShareUrl/Policy/WantedAttribute.php
@@ -32,17 +32,24 @@ class WantedAttribute implements \JsonSerializable
*/
private $acceptSelfAsserted;
+ /**
+ * @var bool|null
+ */
+ private $optional;
+
/**
* @param string $name
* @param string $derivation
* @param bool $acceptSelfAsserted
* @param \Yoti\ShareUrl\Policy\Constraints $constraints
+ * @param bool $optional
*/
public function __construct(
string $name,
- string $derivation = null,
- bool $acceptSelfAsserted = null,
- Constraints $constraints = null
+ ?string $derivation = null,
+ ?bool $acceptSelfAsserted = null,
+ ?Constraints $constraints = null,
+ ?bool $optional = null
) {
Validation::notEmptyString($name, 'name');
$this->name = $name;
@@ -50,6 +57,7 @@ public function __construct(
$this->derivation = $derivation;
$this->acceptSelfAsserted = $acceptSelfAsserted;
$this->constraints = $constraints;
+ $this->optional = $optional;
}
/**
@@ -97,6 +105,14 @@ public function getAcceptSelfAsserted(): ?bool
return $this->acceptSelfAsserted;
}
+ /**
+ * @return bool|null
+ */
+ public function getOptional(): ?bool
+ {
+ return $this->optional;
+ }
+
/**
* @inheritDoc
*
@@ -106,7 +122,7 @@ public function jsonSerialize(): array
{
$json = [
'name' => $this->getName(),
- 'optional' => false,
+ 'optional' => $this->getOptional(),
];
if ($this->getDerivation() !== null) {
@@ -121,6 +137,12 @@ public function jsonSerialize(): array
$json['accept_self_asserted'] = $this->getAcceptSelfAsserted();
}
+ if ($this->getOptional() !== null) {
+ $json['optional'] = $this->getOptional();
+ }
+
+
+
return $json;
}
diff --git a/src/ShareUrl/Policy/WantedAttributeBuilder.php b/src/ShareUrl/Policy/WantedAttributeBuilder.php
index 37f381e7..9995cfdb 100644
--- a/src/ShareUrl/Policy/WantedAttributeBuilder.php
+++ b/src/ShareUrl/Policy/WantedAttributeBuilder.php
@@ -29,6 +29,10 @@ class WantedAttributeBuilder
*/
private $acceptSelfAsserted;
+ /**
+ * @var bool|null
+ */
+ private $optional = false;
/**
* @param string $name
*
@@ -73,6 +77,16 @@ public function withAcceptSelfAsserted(?bool $acceptSelfAsserted = true): self
return $this;
}
+ /**
+ * @param bool $optional
+ *
+ * @return $this
+ */
+ public function withOptional(?bool $optional = false): self
+ {
+ $this->optional = $optional;
+ return $this;
+ }
/**
* @return \Yoti\ShareUrl\Policy\WantedAttribute
*/
@@ -82,7 +96,8 @@ public function build(): WantedAttribute
$this->name,
$this->derivation,
$this->acceptSelfAsserted,
- $this->constraints
+ $this->constraints,
+ $this->optional
);
}
}
diff --git a/src/Util/Json.php b/src/Util/Json.php
index 8be63f78..81f7bdb7 100644
--- a/src/Util/Json.php
+++ b/src/Util/Json.php
@@ -65,7 +65,7 @@ private static function validate(): void
public static function convertFromLatin1ToUtf8Recursively($dat)
{
if (is_string($dat)) {
- return utf8_encode($dat);
+ return mb_convert_encoding($dat, 'UTF-8', 'ISO-8859-1');
} elseif (is_array($dat)) {
$ret = [];
foreach ($dat as $i => $d) {
diff --git a/tests/Aml/ResultTest.php b/tests/Aml/ResultTest.php
index 76dd42d0..86f83eaf 100644
--- a/tests/Aml/ResultTest.php
+++ b/tests/Aml/ResultTest.php
@@ -21,6 +21,11 @@ class ResultTest extends TestCase
*/
public $amlResult;
+ /**
+ * @var \PHPUnit\Framework\MockObject\MockObject&\Psr\Http\Message\ResponseInterface
+ */
+ private $responseMock;
+
public function setup(): void
{
$this->responseMock = $this->createMock(ResponseInterface::class);
diff --git a/tests/Auth/AuthenticationTokenGeneratorTest.php b/tests/Auth/AuthenticationTokenGeneratorTest.php
new file mode 100644
index 00000000..7d2e4dd6
--- /dev/null
+++ b/tests/Auth/AuthenticationTokenGeneratorTest.php
@@ -0,0 +1,54 @@
+assertInstanceOf(\Yoti\Auth\Builder::class, $builder);
+ }
+
+ /**
+ * @test
+ * @covers ::generate
+ */
+ public function shouldThrowOnEmptyScopes()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+
+ $generator = new AuthenticationTokenGenerator(
+ self::SOME_SDK_ID,
+ $pemFile,
+ function () {
+ return self::SOME_JWT_ID;
+ },
+ self::SOME_AUTH_URL
+ );
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('scopes must not be empty');
+
+ $generator->generate([]);
+ }
+}
diff --git a/tests/Auth/BuilderTest.php b/tests/Auth/BuilderTest.php
new file mode 100644
index 00000000..d1fbb157
--- /dev/null
+++ b/tests/Auth/BuilderTest.php
@@ -0,0 +1,139 @@
+withSdkId(self::SOME_SDK_ID)
+ ->withPemFile($pemFile)
+ ->build();
+
+ $this->assertInstanceOf(AuthenticationTokenGenerator::class, $generator);
+ }
+
+ /**
+ * @test
+ * @covers ::withSdkId
+ * @covers ::withPemFilePath
+ * @covers ::build
+ */
+ public function shouldBuildWithPemFilePath()
+ {
+ $generator = (new Builder())
+ ->withSdkId(self::SOME_SDK_ID)
+ ->withPemFilePath(TestData::PEM_FILE)
+ ->build();
+
+ $this->assertInstanceOf(AuthenticationTokenGenerator::class, $generator);
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenSdkIdIsEmpty()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage("'sdkId' must not be empty or null");
+
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+
+ (new Builder())
+ ->withSdkId('')
+ ->withPemFile($pemFile)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenSdkIdIsMissing()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage("'sdkId' must not be empty or null");
+
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+
+ (new Builder())
+ ->withPemFile($pemFile)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenPemFileIsMissing()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage("'pemFile' must not be null");
+
+ (new Builder())
+ ->withSdkId(self::SOME_SDK_ID)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::withJwtIdSupplier
+ * @covers ::build
+ */
+ public function shouldAcceptCustomJwtIdSupplier()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+
+ $generator = (new Builder())
+ ->withSdkId(self::SOME_SDK_ID)
+ ->withPemFile($pemFile)
+ ->withJwtIdSupplier(function () {
+ return 'custom-jwt-id';
+ })
+ ->build();
+
+ $this->assertInstanceOf(AuthenticationTokenGenerator::class, $generator);
+ }
+
+ /**
+ * @test
+ * @covers ::withAuthApiUrl
+ * @covers ::build
+ */
+ public function shouldAcceptCustomAuthApiUrl()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+
+ $generator = (new Builder())
+ ->withSdkId(self::SOME_SDK_ID)
+ ->withPemFile($pemFile)
+ ->withAuthApiUrl(self::SOME_AUTH_URL)
+ ->build();
+
+ $this->assertInstanceOf(AuthenticationTokenGenerator::class, $generator);
+ }
+}
diff --git a/tests/Auth/CreateAuthenticationTokenResponseTest.php b/tests/Auth/CreateAuthenticationTokenResponseTest.php
new file mode 100644
index 00000000..bb821b0d
--- /dev/null
+++ b/tests/Auth/CreateAuthenticationTokenResponseTest.php
@@ -0,0 +1,91 @@
+ self::SOME_ACCESS_TOKEN,
+ 'token_type' => self::SOME_TOKEN_TYPE,
+ 'expires_in' => self::SOME_EXPIRES_IN,
+ 'scope' => self::SOME_SCOPE,
+ ]);
+
+ $this->assertEquals(self::SOME_ACCESS_TOKEN, $response->getAccessToken());
+ $this->assertEquals(self::SOME_TOKEN_TYPE, $response->getTokenType());
+ $this->assertEquals(self::SOME_EXPIRES_IN, $response->getExpiresIn());
+ $this->assertEquals(self::SOME_SCOPE, $response->getScope());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getExpiresIn
+ * @covers ::getScope
+ */
+ public function shouldHandleMissingOptionalFields()
+ {
+ $response = new CreateAuthenticationTokenResponse([
+ 'access_token' => self::SOME_ACCESS_TOKEN,
+ 'token_type' => self::SOME_TOKEN_TYPE,
+ ]);
+
+ $this->assertEquals(self::SOME_ACCESS_TOKEN, $response->getAccessToken());
+ $this->assertEquals(self::SOME_TOKEN_TYPE, $response->getTokenType());
+ $this->assertNull($response->getExpiresIn());
+ $this->assertNull($response->getScope());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldHandleEmptyResponseData()
+ {
+ $response = new CreateAuthenticationTokenResponse([]);
+
+ $this->assertEquals('', $response->getAccessToken());
+ $this->assertEquals('', $response->getTokenType());
+ $this->assertNull($response->getExpiresIn());
+ $this->assertNull($response->getScope());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getExpiresIn
+ */
+ public function shouldCastExpiresInToInteger()
+ {
+ $response = new CreateAuthenticationTokenResponse([
+ 'access_token' => self::SOME_ACCESS_TOKEN,
+ 'token_type' => self::SOME_TOKEN_TYPE,
+ 'expires_in' => '7200',
+ ]);
+
+ $this->assertSame(7200, $response->getExpiresIn());
+ }
+}
diff --git a/tests/DigitalIdentityClientBuilderTest.php b/tests/DigitalIdentityClientBuilderTest.php
new file mode 100644
index 00000000..abd741d2
--- /dev/null
+++ b/tests/DigitalIdentityClientBuilderTest.php
@@ -0,0 +1,131 @@
+withClientSdkId(self::SOME_SDK_ID)
+ ->withPemFilePath(TestData::PEM_FILE)
+ ->build();
+
+ $this->assertInstanceOf(DigitalIdentityClient::class, $client);
+ }
+
+ /**
+ * @test
+ * @covers ::withAuthenticationToken
+ * @covers ::build
+ */
+ public function shouldBuildWithAuthenticationToken()
+ {
+ $client = DigitalIdentityClient::builder()
+ ->withAuthenticationToken(self::SOME_AUTH_TOKEN)
+ ->build();
+
+ $this->assertInstanceOf(DigitalIdentityClient::class, $client);
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenAuthTokenSetWithSdkId()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Must not supply sdkId or PEM file when using an authentication token');
+
+ DigitalIdentityClient::builder()
+ ->withAuthenticationToken(self::SOME_AUTH_TOKEN)
+ ->withClientSdkId(self::SOME_SDK_ID)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenAuthTokenSetWithPem()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Must not supply sdkId or PEM file when using an authentication token');
+
+ DigitalIdentityClient::builder()
+ ->withAuthenticationToken(self::SOME_AUTH_TOKEN)
+ ->withPemFilePath(TestData::PEM_FILE)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenNoSdkIdForSignedRequest()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('An sdkId and PEM file must be provided when not using an authentication token');
+
+ DigitalIdentityClient::builder()
+ ->withPemFilePath(TestData::PEM_FILE)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenNoPemForSignedRequest()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('An sdkId and PEM file must be provided when not using an authentication token');
+
+ DigitalIdentityClient::builder()
+ ->withClientSdkId(self::SOME_SDK_ID)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenNothingProvided()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('An sdkId and PEM file must be provided when not using an authentication token');
+
+ DigitalIdentityClient::builder()
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::withAuthenticationToken
+ * @covers ::build
+ */
+ public function shouldThrowOnEmptyAuthToken()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+
+ DigitalIdentityClient::builder()
+ ->withAuthenticationToken('')
+ ->build();
+ }
+}
diff --git a/tests/DocScan/DocScanClientBuilderTest.php b/tests/DocScan/DocScanClientBuilderTest.php
new file mode 100644
index 00000000..826c0952
--- /dev/null
+++ b/tests/DocScan/DocScanClientBuilderTest.php
@@ -0,0 +1,149 @@
+withClientSdkId(self::SOME_SDK_ID)
+ ->withPemFilePath(TestData::PEM_FILE)
+ ->build();
+
+ $this->assertInstanceOf(DocScanClient::class, $client);
+ }
+
+ /**
+ * @test
+ * @covers ::withAuthenticationToken
+ * @covers ::build
+ */
+ public function shouldBuildWithAuthenticationToken()
+ {
+ $client = DocScanClient::builder()
+ ->withAuthenticationToken(self::SOME_AUTH_TOKEN)
+ ->build();
+
+ $this->assertInstanceOf(DocScanClient::class, $client);
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenAuthTokenSetWithSdkId()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Must not supply sdkId or PEM file when using an authentication token');
+
+ DocScanClient::builder()
+ ->withAuthenticationToken(self::SOME_AUTH_TOKEN)
+ ->withClientSdkId(self::SOME_SDK_ID)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenAuthTokenSetWithPem()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Must not supply sdkId or PEM file when using an authentication token');
+
+ DocScanClient::builder()
+ ->withAuthenticationToken(self::SOME_AUTH_TOKEN)
+ ->withPemFilePath(TestData::PEM_FILE)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenNoSdkIdForSignedRequest()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('An sdkId and PEM file must be provided when not using an authentication token');
+
+ DocScanClient::builder()
+ ->withPemFilePath(TestData::PEM_FILE)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenNoPemForSignedRequest()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('An sdkId and PEM file must be provided when not using an authentication token');
+
+ DocScanClient::builder()
+ ->withClientSdkId(self::SOME_SDK_ID)
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ */
+ public function shouldThrowWhenNothingProvided()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('An sdkId and PEM file must be provided when not using an authentication token');
+
+ DocScanClient::builder()
+ ->build();
+ }
+
+ /**
+ * @test
+ * @covers ::withOptions
+ * @covers ::build
+ */
+ public function shouldAcceptCustomOptions()
+ {
+ $client = DocScanClient::builder()
+ ->withAuthenticationToken(self::SOME_AUTH_TOKEN)
+ ->withOptions([Config::SDK_IDENTIFIER => 'CustomSDK'])
+ ->build();
+
+ $this->assertInstanceOf(DocScanClient::class, $client);
+ }
+
+ /**
+ * @test
+ * @covers ::withAuthenticationToken
+ * @covers ::build
+ */
+ public function shouldThrowOnEmptyAuthToken()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+
+ DocScanClient::builder()
+ ->withAuthenticationToken('')
+ ->build();
+ }
+}
diff --git a/tests/DocScan/DocScanClientTest.php b/tests/DocScan/DocScanClientTest.php
index 306e1d17..623c0a60 100644
--- a/tests/DocScan/DocScanClientTest.php
+++ b/tests/DocScan/DocScanClientTest.php
@@ -6,6 +6,7 @@
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
use Yoti\DocScan\DocScanClient;
use Yoti\DocScan\Session\Create\CreateSessionResult;
use Yoti\DocScan\Session\Create\FaceCapture\CreateFaceCaptureResourcePayload;
@@ -92,8 +93,12 @@ public function testEmptyApiUrlEnvironmentVariable()
*/
private function assertApiUrlStartsWith($expectedUrl, $clientApiUrl = null)
{
+ $stream = $this->createMock(\Psr\Http\Message\StreamInterface::class);
+ $stream->method('getContents')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_CREATION_RESPONSE));
+ $stream->method('__toString')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_CREATION_RESPONSE));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_CREATION_RESPONSE));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -126,8 +131,13 @@ private function assertApiUrlStartsWith($expectedUrl, $clientApiUrl = null)
*/
public function testCreateSession()
{
+ $stream = $this->createMock(\Psr\Http\Message\StreamInterface::class);
+ $stream->method('getContents')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_CREATION_RESPONSE));
+ $stream->method('__toString')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_CREATION_RESPONSE));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_CREATION_RESPONSE));
+ $response->method('getBody')->willReturn($stream);
+
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -155,8 +165,12 @@ public function testCreateSession()
*/
public function testGetSession()
{
+ $stream = $this->createMock(\Psr\Http\Message\StreamInterface::class);
+ $stream->method('getContents')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_RESPONSE));
+ $stream->method('__toString')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_RESPONSE));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_RESPONSE));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -203,8 +217,13 @@ public function testDeleteSessionDoesNotThrowException()
*/
public function testGetMedia()
{
+ $stream = $this->createMock(\Psr\Http\Message\StreamInterface::class);
+ $stream->method('getContents')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_RESPONSE));
+ $stream->method('__toString')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_RESPONSE));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(file_get_contents(TestData::DOC_SCAN_SESSION_RESPONSE));
+ $response->method('getBody')->willReturn($stream);
+
$response->method('getStatusCode')->willReturn(200);
$response->method('getHeader')->willReturn([ 'image/png' ]);
@@ -276,8 +295,12 @@ public function testDeleteMediaDoesNotThrowException()
*/
public function testGetSupportedDocuments()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -301,8 +324,12 @@ public function testGetSupportedDocuments()
*/
public function testCreateFaceCaptureResource()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(201);
$createFaceCaptureResourcePayloadMock = $this->createMock(CreateFaceCaptureResourcePayload::class);
@@ -331,9 +358,14 @@ public function testCreateFaceCaptureResource()
*/
public function testUploadFaceCaptureImage()
{
+
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn($stream);
$uploadFaceCaptureImagePayloadMock = $this->createMock(UploadFaceCaptureImagePayload::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -358,8 +390,12 @@ public function testUploadFaceCaptureImage()
*/
public function testGetSessionConfiguration()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -383,9 +419,13 @@ public function testGetSessionConfiguration()
*/
public function testPutIbvInstructions()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn($stream);
$instructionsMock = $this->createMock(Instructions::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -409,8 +449,12 @@ public function testPutIbvInstructions()
*/
public function testGetIbvInstructions()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -433,8 +477,12 @@ public function testGetIbvInstructions()
*/
public function testGetIbvInstructionsPdf()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -457,8 +505,12 @@ public function testGetIbvInstructionsPdf()
*/
public function testFetchInstructionsContactProfile()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -481,8 +533,12 @@ public function testFetchInstructionsContactProfile()
*/
public function testTriggerIbvEmailNotification()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(json_encode((object)[]));
+ $stream->method('__toString')->willReturn(json_encode((object)[]));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(json_encode((object)[]));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
diff --git a/tests/DocScan/Session/Create/ApplicantProfileBuilderTest.php b/tests/DocScan/Session/Create/ApplicantProfileBuilderTest.php
new file mode 100644
index 00000000..bb000cf3
--- /dev/null
+++ b/tests/DocScan/Session/Create/ApplicantProfileBuilderTest.php
@@ -0,0 +1,142 @@
+withFullName(self::SOME_FULL_NAME)
+ ->build();
+
+ $this->assertEquals(self::SOME_FULL_NAME, $profile->getFullName());
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers ::withDateOfBirth
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::__construct
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::getDateOfBirth
+ */
+ public function shouldBuildWithDateOfBirth()
+ {
+ $profile = (new ApplicantProfileBuilder())
+ ->withDateOfBirth(self::SOME_DATE_OF_BIRTH)
+ ->build();
+
+ $this->assertEquals(self::SOME_DATE_OF_BIRTH, $profile->getDateOfBirth());
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers ::withNamePrefix
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::__construct
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::getNamePrefix
+ */
+ public function shouldBuildWithNamePrefix()
+ {
+ $profile = (new ApplicantProfileBuilder())
+ ->withNamePrefix(self::SOME_NAME_PREFIX)
+ ->build();
+
+ $this->assertEquals(self::SOME_NAME_PREFIX, $profile->getNamePrefix());
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers ::withStructuredPostalAddress
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::__construct
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::getStructuredPostalAddress
+ */
+ public function shouldBuildWithStructuredPostalAddress()
+ {
+ $address = (new StructuredPostalAddressBuilder())
+ ->withBuildingNumber('74')
+ ->withPostalCode('E143RN')
+ ->build();
+
+ $profile = (new ApplicantProfileBuilder())
+ ->withStructuredPostalAddress($address)
+ ->build();
+
+ $this->assertEquals('74', $profile->getStructuredPostalAddress()->getBuildingNumber());
+ $this->assertEquals('E143RN', $profile->getStructuredPostalAddress()->getPostalCode());
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::jsonSerialize
+ */
+ public function shouldCorrectlySerializeWithAllProperties()
+ {
+ $address = (new StructuredPostalAddressBuilder())
+ ->withAddressFormat(1)
+ ->withBuildingNumber('74')
+ ->withAddressLine1('AddressLine1')
+ ->withTownCity('CityName')
+ ->withPostalCode('E143RN')
+ ->withCountryIso('GBR')
+ ->withCountry('United Kingdom')
+ ->build();
+
+ $profile = (new ApplicantProfileBuilder())
+ ->withFullName(self::SOME_FULL_NAME)
+ ->withDateOfBirth(self::SOME_DATE_OF_BIRTH)
+ ->withNamePrefix(self::SOME_NAME_PREFIX)
+ ->withStructuredPostalAddress($address)
+ ->build();
+
+ $json = json_encode($profile);
+
+ $this->assertStringContainsString('"full_name":"John Doe"', $json);
+ $this->assertStringContainsString('"date_of_birth":"1988-11-02"', $json);
+ $this->assertStringContainsString('"name_prefix":"Mr"', $json);
+ $this->assertStringContainsString('"structured_postal_address"', $json);
+ $this->assertStringContainsString('"building_number":"74"', $json);
+ $this->assertStringContainsString('"country_iso":"GBR"', $json);
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\ApplicantProfile::jsonSerialize
+ */
+ public function shouldSerializeWithoutNullValues()
+ {
+ $profile = (new ApplicantProfileBuilder())
+ ->withFullName(self::SOME_FULL_NAME)
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode([
+ 'full_name' => self::SOME_FULL_NAME,
+ ]),
+ json_encode($profile)
+ );
+ }
+}
diff --git a/tests/DocScan/Session/Create/ResourceCreationContainerBuilderTest.php b/tests/DocScan/Session/Create/ResourceCreationContainerBuilderTest.php
new file mode 100644
index 00000000..4ad23b4d
--- /dev/null
+++ b/tests/DocScan/Session/Create/ResourceCreationContainerBuilderTest.php
@@ -0,0 +1,93 @@
+withFullName('John Doe')
+ ->withDateOfBirth('1988-11-02')
+ ->build();
+
+ $container = (new ResourceCreationContainerBuilder())
+ ->withApplicantProfile($applicantProfile)
+ ->build();
+
+ $this->assertEquals($applicantProfile, $container->getApplicantProfile());
+ $this->assertEquals('John Doe', $container->getApplicantProfile()->getFullName());
+ $this->assertEquals('1988-11-02', $container->getApplicantProfile()->getDateOfBirth());
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\ResourceCreationContainer::jsonSerialize
+ */
+ public function shouldCorrectlySerializeApplicantProfile()
+ {
+ $address = (new StructuredPostalAddressBuilder())
+ ->withAddressFormat(1)
+ ->withBuildingNumber('74')
+ ->withAddressLine1('AddressLine1')
+ ->withTownCity('CityName')
+ ->withPostalCode('E143RN')
+ ->withCountryIso('GBR')
+ ->withCountry('United Kingdom')
+ ->build();
+
+ $applicantProfile = (new ApplicantProfileBuilder())
+ ->withFullName('John Doe')
+ ->withDateOfBirth('1988-11-02')
+ ->withNamePrefix('Mr')
+ ->withStructuredPostalAddress($address)
+ ->build();
+
+ $container = (new ResourceCreationContainerBuilder())
+ ->withApplicantProfile($applicantProfile)
+ ->build();
+
+ $json = json_encode($container);
+
+ $this->assertStringContainsString('"applicant_profile"', $json);
+ $this->assertStringContainsString('"full_name":"John Doe"', $json);
+ $this->assertStringContainsString('"date_of_birth":"1988-11-02"', $json);
+ $this->assertStringContainsString('"name_prefix":"Mr"', $json);
+ $this->assertStringContainsString('"building_number":"74"', $json);
+ $this->assertStringContainsString('"country_iso":"GBR"', $json);
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\ResourceCreationContainer::jsonSerialize
+ */
+ public function shouldSerializeWithoutNullValues()
+ {
+ $container = (new ResourceCreationContainerBuilder())
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode(new \stdClass()),
+ json_encode($container)
+ );
+ }
+}
diff --git a/tests/DocScan/Session/Create/SdkConfigBuilderTest.php b/tests/DocScan/Session/Create/SdkConfigBuilderTest.php
index 3c84f5d2..3c879bc3 100644
--- a/tests/DocScan/Session/Create/SdkConfigBuilderTest.php
+++ b/tests/DocScan/Session/Create/SdkConfigBuilderTest.php
@@ -23,7 +23,11 @@ class SdkConfigBuilderTest extends TestCase
private const SOME_CATEGORY = 'someCategory';
private const SOME_NUMBER_RETRIES = 5;
private const SOME_BIOMETRIC_CONSENT_FLOW = 'someBiometricConsentFlow';
-
+ private const SOME_DARK_MODE = 'someDarkMode';
+ private const SOME_PRIMARY_COLOUR_DARK_MODE = 'somePrimaryColourDarkMode';
+ private const SOME_BRAND_ID = 'someBrandId';
+ private const SOME_SCREEN_IDENTIFIER = 'someScreenIdentifier';
+ private const ANOTHER_SCREEN_IDENTIFIER = 'anotherScreenIdentifier';
/**
* @test
@@ -38,6 +42,7 @@ class SdkConfigBuilderTest extends TestCase
* @covers ::withErrorUrl
* @covers ::withPrivacyPolicyUrl
* @covers ::withAllowHandoff
+ * @covers ::withBrandId
* @covers \Yoti\DocScan\Session\Create\SdkConfig::__construct
* @covers \Yoti\DocScan\Session\Create\SdkConfig::getAllowedCaptureMethods
* @covers \Yoti\DocScan\Session\Create\SdkConfig::getPrimaryColour
@@ -49,6 +54,9 @@ class SdkConfigBuilderTest extends TestCase
* @covers \Yoti\DocScan\Session\Create\SdkConfig::getErrorUrl
* @covers \Yoti\DocScan\Session\Create\SdkConfig::getPrivacyPolicyUrl
* @covers \Yoti\DocScan\Session\Create\SdkConfig::getAllowHandoff
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::getDarkMode
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::getPrimaryColourDarkMode
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::getBrandId
*/
public function shouldCorrectlyBuildSdkConfig()
{
@@ -63,6 +71,10 @@ public function shouldCorrectlyBuildSdkConfig()
->withErrorUrl(self::SOME_ERROR_URL)
->withPrivacyPolicyUrl(self::SOME_PRIVACY_POLICY_URL)
->withAllowHandoff(true)
+ ->withBiometricConsentFlow(self::SOME_BIOMETRIC_CONSENT_FLOW)
+ ->withDarkMode(self::SOME_DARK_MODE)
+ ->withPrimaryColourDarkMode(self::SOME_PRIMARY_COLOUR_DARK_MODE)
+ ->withBrandId(self::SOME_BRAND_ID)
->build();
$this->assertEquals(self::SOME_CAPTURE_METHOD, $result->getAllowedCaptureMethods());
@@ -74,7 +86,11 @@ public function shouldCorrectlyBuildSdkConfig()
$this->assertEquals(self::SOME_SUCCESS_URL, $result->getSuccessUrl());
$this->assertEquals(self::SOME_ERROR_URL, $result->getErrorUrl());
$this->assertEquals(self::SOME_PRIVACY_POLICY_URL, $result->getPrivacyPolicyUrl());
+ $this->assertEquals(self::SOME_BIOMETRIC_CONSENT_FLOW, $result->getBiometricConsentFlow());
$this->assertTrue($result->getAllowHandoff());
+ $this->assertEquals(self::SOME_DARK_MODE, $result->getDarkMode());
+ $this->assertEquals(self::SOME_PRIMARY_COLOUR_DARK_MODE, $result->getPrimaryColourDarkMode());
+ $this->assertEquals(self::SOME_BRAND_ID, $result->getBrandId());
}
/**
@@ -121,6 +137,8 @@ public function shouldProduceTheCorrectJsonString()
->withPrivacyPolicyUrl(self::SOME_PRIVACY_POLICY_URL)
->withAllowHandoff(true)
->withBiometricConsentFlow(self::SOME_BIOMETRIC_CONSENT_FLOW)
+ ->withPrimaryColourDarkMode(self::SOME_PRIMARY_COLOUR_DARK_MODE)
+ ->withDarkMode(self::SOME_DARK_MODE)
->build();
$expected = [
@@ -134,7 +152,9 @@ public function shouldProduceTheCorrectJsonString()
'error_url' => self::SOME_ERROR_URL,
'privacy_policy_url' => self::SOME_PRIVACY_POLICY_URL,
'allow_handoff' => true,
- 'biometric_consent_flow' => self::SOME_BIOMETRIC_CONSENT_FLOW
+ 'biometric_consent_flow' => self::SOME_BIOMETRIC_CONSENT_FLOW,
+ 'dark_mode' => self::SOME_DARK_MODE,
+ 'primary_colour_dark_mode' => self::SOME_PRIMARY_COLOUR_DARK_MODE
];
$this->assertJsonStringEqualsJsonString(json_encode($expected), json_encode($result));
@@ -291,4 +311,133 @@ public function attemptsConfigurationShouldAllowMultipleCategories(): void
->getIdDocumentTextDataExtraction()
);
}
+
+ /**
+ * @test
+ * @covers ::withDarkModeAuto
+ */
+ public function shouldSetCorrectValueWithDarkModeAuto()
+ {
+ $result = (new SdkConfigBuilder())
+ ->withDarkModeAuto()
+ ->build();
+
+ $this->assertEquals('AUTO', $result->getDarkMode());
+ }
+
+ /**
+ * @test
+ * @covers ::withDarkModeOn
+ */
+ public function shouldSetCorrectValueWithDarkModeOn()
+ {
+ $result = (new SdkConfigBuilder())
+ ->withDarkModeOn()
+ ->build();
+
+ $this->assertEquals('ON', $result->getDarkMode());
+ }
+
+ /**
+ * @test
+ * @covers ::withDarkModeOff
+ */
+ public function shouldSetCorrectValueWithDarkModeOff()
+ {
+ $result = (new SdkConfigBuilder())
+ ->withDarkModeOff()
+ ->build();
+
+ $this->assertEquals('OFF', $result->getDarkMode());
+ }
+
+ /**
+ * @test
+ * @covers ::withSuppressedScreens
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::getSuppressedScreens
+ */
+ public function shouldSetSuppressedScreensArray()
+ {
+ $suppressedScreens = [self::SOME_SCREEN_IDENTIFIER, self::ANOTHER_SCREEN_IDENTIFIER];
+
+ $result = (new SdkConfigBuilder())
+ ->withSuppressedScreens($suppressedScreens)
+ ->build();
+
+ $this->assertEquals($suppressedScreens, $result->getSuppressedScreens());
+ }
+
+ /**
+ * @test
+ * @covers ::withSuppressedScreen
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::getSuppressedScreens
+ */
+ public function shouldAddSingleSuppressedScreen()
+ {
+ $result = (new SdkConfigBuilder())
+ ->withSuppressedScreen(self::SOME_SCREEN_IDENTIFIER)
+ ->build();
+
+ $this->assertEquals([self::SOME_SCREEN_IDENTIFIER], $result->getSuppressedScreens());
+ }
+
+ /**
+ * @test
+ * @covers ::withSuppressedScreen
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::getSuppressedScreens
+ */
+ public function shouldAddMultipleSuppressedScreensIndividually()
+ {
+ $result = (new SdkConfigBuilder())
+ ->withSuppressedScreen(self::SOME_SCREEN_IDENTIFIER)
+ ->withSuppressedScreen(self::ANOTHER_SCREEN_IDENTIFIER)
+ ->build();
+
+ $expectedScreens = [self::SOME_SCREEN_IDENTIFIER, self::ANOTHER_SCREEN_IDENTIFIER];
+ $this->assertEquals($expectedScreens, $result->getSuppressedScreens());
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::getSuppressedScreens
+ */
+ public function shouldReturnNullWhenNoSuppressedScreensSet()
+ {
+ $result = (new SdkConfigBuilder())
+ ->build();
+
+ $this->assertNull($result->getSuppressedScreens());
+ }
+
+ /**
+ * @test
+ * @covers ::withSuppressedScreens
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::jsonSerialize
+ */
+ public function shouldIncludeSuppressedScreensInJsonSerialization()
+ {
+ $suppressedScreens = [self::SOME_SCREEN_IDENTIFIER, self::ANOTHER_SCREEN_IDENTIFIER];
+
+ $result = (new SdkConfigBuilder())
+ ->withSuppressedScreens($suppressedScreens)
+ ->build();
+
+ $jsonData = $result->jsonSerialize();
+ $this->assertEquals($suppressedScreens, $jsonData->suppressed_screens);
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\SdkConfig::jsonSerialize
+ */
+ public function shouldNotIncludeSuppressedScreensInJsonWhenNull()
+ {
+ $result = (new SdkConfigBuilder())
+ ->build();
+
+ $jsonData = $result->jsonSerialize();
+ $this->assertFalse(property_exists($jsonData, 'suppressed_screens'));
+ }
}
diff --git a/tests/DocScan/Session/Create/SessionSpecificationBuilderTest.php b/tests/DocScan/Session/Create/SessionSpecificationBuilderTest.php
index 3f3640b7..f1d274b2 100644
--- a/tests/DocScan/Session/Create/SessionSpecificationBuilderTest.php
+++ b/tests/DocScan/Session/Create/SessionSpecificationBuilderTest.php
@@ -9,6 +9,7 @@
use Yoti\DocScan\Session\Create\IbvOptions;
use Yoti\DocScan\Session\Create\ImportToken;
use Yoti\DocScan\Session\Create\NotificationConfig;
+use Yoti\DocScan\Session\Create\ResourceCreationContainer;
use Yoti\DocScan\Session\Create\SdkConfig;
use Yoti\DocScan\Session\Create\SessionSpecificationBuilder;
use Yoti\DocScan\Session\Create\Task\RequestedTask;
@@ -63,11 +64,21 @@ class SessionSpecificationBuilderTest extends TestCase
*/
private $identityProfileRequirements;
+ /**
+ * @var object
+ */
+ private $advancedIdentityProfileRequirements;
+
/**
* @var ImportToken
*/
private $importTokenMock;
+ /**
+ * @var ResourceCreationContainer
+ */
+ private $resourcesMock;
+
public function setup(): void
{
$this->sdkConfigMock = $this->createMock(SdkConfig::class);
@@ -89,6 +100,11 @@ public function setup(): void
$this->importTokenMock = $this->createMock(ImportToken::class);
+ $this->resourcesMock = $this->createMock(ResourceCreationContainer::class);
+ $this->resourcesMock
+ ->method('jsonSerialize')
+ ->willReturn((object)['applicant_profile' => (object)['full_name' => 'John Doe']]);
+
$this->subject = (object)[1 => 'some'];
$this->identityProfileRequirements = (object)[
@@ -98,6 +114,17 @@ public function setup(): void
'objective' => 'STANDARD'
]
];
+
+ $this->advancedIdentityProfileRequirements = (object)[
+ 'profiles' => [
+ [
+ 'trust_framework' => 'UK_TFIDA',
+ 'schemes' => [
+ ['type' => 'DBS', 'objective' => 'STANDARD']
+ ]
+ ]
+ ]
+ ];
}
/**
@@ -532,4 +559,115 @@ public function shouldReturnCorrectJsonStringWithImportToken()
json_encode($sessionSpecification)
);
}
+
+ /**
+ * @test
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::getAdvancedIdentityProfileRequirements
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::__construct
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::withAdvancedIdentityProfileRequirements
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::build
+ */
+ public function shouldBuildWithAdvancedIdentityProfileRequirements()
+ {
+ $sessionSpecificationResult = (new SessionSpecificationBuilder())
+ ->withAdvancedIdentityProfileRequirements($this->advancedIdentityProfileRequirements)
+ ->build();
+
+ $this->assertEquals(
+ $this->advancedIdentityProfileRequirements,
+ $sessionSpecificationResult->getAdvancedIdentityProfileRequirements()
+ );
+ }
+
+ /**
+ * @test
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::getAdvancedIdentityProfileRequirements
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::__construct
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::build
+ */
+ public function shouldNotImplicitlySetAValueForAdvancedIdentityProfileRequirements()
+ {
+ $sessionSpecificationResult = (new SessionSpecificationBuilder())
+ ->build();
+
+ $this->assertNull($sessionSpecificationResult->getAdvancedIdentityProfileRequirements());
+ }
+
+ /**
+ * @test
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::jsonSerialize
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::withAdvancedIdentityProfileRequirements
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::build
+ */
+ public function shouldReturnCorrectJsonStringWithAdvancedIdentityProfileRequirements()
+ {
+ $sessionSpecification = (new SessionSpecificationBuilder())
+ ->withAdvancedIdentityProfileRequirements($this->advancedIdentityProfileRequirements)
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode([
+ 'requested_checks' => [],
+ 'requested_tasks' => [],
+ 'required_documents' => [],
+ 'create_identity_profile_preview' => false,
+ 'advanced_identity_profile_requirements' => $this->advancedIdentityProfileRequirements,
+ ]),
+ json_encode($sessionSpecification)
+ );
+ }
+
+ /**
+ * @test
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::getResources
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::__construct
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::withResources
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::build
+ */
+ public function shouldBuildWithResources()
+ {
+ $sessionSpecificationResult = (new SessionSpecificationBuilder())
+ ->withResources($this->resourcesMock)
+ ->build();
+
+ $this->assertEquals($this->resourcesMock, $sessionSpecificationResult->getResources());
+ }
+
+ /**
+ * @test
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::getResources
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::__construct
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::build
+ */
+ public function shouldNotImplicitlySetAValueForResources()
+ {
+ $sessionSpecificationResult = (new SessionSpecificationBuilder())
+ ->build();
+
+ $this->assertNull($sessionSpecificationResult->getResources());
+ }
+
+ /**
+ * @test
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecification::jsonSerialize
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::withResources
+ * @covers \Yoti\DocScan\Session\Create\SessionSpecificationBuilder::build
+ */
+ public function shouldReturnCorrectJsonStringWithResources()
+ {
+ $sessionSpecification = (new SessionSpecificationBuilder())
+ ->withResources($this->resourcesMock)
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode([
+ 'requested_checks' => [],
+ 'requested_tasks' => [],
+ 'required_documents' => [],
+ 'create_identity_profile_preview' => false,
+ 'resources' => $this->resourcesMock,
+ ]),
+ json_encode($sessionSpecification)
+ );
+ }
}
diff --git a/tests/DocScan/Session/Create/StructuredPostalAddressBuilderTest.php b/tests/DocScan/Session/Create/StructuredPostalAddressBuilderTest.php
new file mode 100644
index 00000000..ad4f6a80
--- /dev/null
+++ b/tests/DocScan/Session/Create/StructuredPostalAddressBuilderTest.php
@@ -0,0 +1,121 @@
+withAddressFormat(self::SOME_ADDRESS_FORMAT)
+ ->withBuildingNumber(self::SOME_BUILDING_NUMBER)
+ ->withAddressLine1(self::SOME_ADDRESS_LINE_1)
+ ->withTownCity(self::SOME_TOWN_CITY)
+ ->withPostalCode(self::SOME_POSTAL_CODE)
+ ->withCountryIso(self::SOME_COUNTRY_ISO)
+ ->withCountry(self::SOME_COUNTRY)
+ ->withFormattedAddress(self::SOME_FORMATTED_ADDRESS)
+ ->build();
+
+ $this->assertEquals(self::SOME_ADDRESS_FORMAT, $address->getAddressFormat());
+ $this->assertEquals(self::SOME_BUILDING_NUMBER, $address->getBuildingNumber());
+ $this->assertEquals(self::SOME_ADDRESS_LINE_1, $address->getAddressLine1());
+ $this->assertEquals(self::SOME_TOWN_CITY, $address->getTownCity());
+ $this->assertEquals(self::SOME_POSTAL_CODE, $address->getPostalCode());
+ $this->assertEquals(self::SOME_COUNTRY_ISO, $address->getCountryIso());
+ $this->assertEquals(self::SOME_COUNTRY, $address->getCountry());
+ $this->assertEquals(self::SOME_FORMATTED_ADDRESS, $address->getFormattedAddress());
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\StructuredPostalAddress::jsonSerialize
+ */
+ public function shouldCorrectlySerialize()
+ {
+ $address = (new StructuredPostalAddressBuilder())
+ ->withAddressFormat(self::SOME_ADDRESS_FORMAT)
+ ->withBuildingNumber(self::SOME_BUILDING_NUMBER)
+ ->withAddressLine1(self::SOME_ADDRESS_LINE_1)
+ ->withTownCity(self::SOME_TOWN_CITY)
+ ->withPostalCode(self::SOME_POSTAL_CODE)
+ ->withCountryIso(self::SOME_COUNTRY_ISO)
+ ->withCountry(self::SOME_COUNTRY)
+ ->withFormattedAddress(self::SOME_FORMATTED_ADDRESS)
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode([
+ 'address_format' => self::SOME_ADDRESS_FORMAT,
+ 'building_number' => self::SOME_BUILDING_NUMBER,
+ 'address_line1' => self::SOME_ADDRESS_LINE_1,
+ 'town_city' => self::SOME_TOWN_CITY,
+ 'postal_code' => self::SOME_POSTAL_CODE,
+ 'country_iso' => self::SOME_COUNTRY_ISO,
+ 'country' => self::SOME_COUNTRY,
+ 'formatted_address' => self::SOME_FORMATTED_ADDRESS,
+ ]),
+ json_encode($address)
+ );
+ }
+
+ /**
+ * @test
+ * @covers ::build
+ * @covers \Yoti\DocScan\Session\Create\StructuredPostalAddress::jsonSerialize
+ */
+ public function shouldSerializeWithoutNullValues()
+ {
+ $address = (new StructuredPostalAddressBuilder())
+ ->withBuildingNumber(self::SOME_BUILDING_NUMBER)
+ ->withPostalCode(self::SOME_POSTAL_CODE)
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode([
+ 'building_number' => self::SOME_BUILDING_NUMBER,
+ 'postal_code' => self::SOME_POSTAL_CODE,
+ ]),
+ json_encode($address)
+ );
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/AdvancedIdentityProfilePreviewResponseTest.php b/tests/DocScan/Session/Retrieve/AdvancedIdentityProfilePreviewResponseTest.php
new file mode 100644
index 00000000..54dad827
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/AdvancedIdentityProfilePreviewResponseTest.php
@@ -0,0 +1,47 @@
+ [
+ 'id' => 'SOME_ID',
+ 'type' => 'JSON',
+ 'created' => '2021-06-11T11:39:24Z',
+ 'last_updated' => '2021-06-11T11:39:24Z',
+ ]
+ ];
+
+ $result = new AdvancedIdentityProfilePreviewResponse($data);
+
+ $this->assertInstanceOf(AdvancedIdentityProfilePreviewResponse::class, $result);
+ $this->assertInstanceOf(MediaResponse::class, $result->getMedia());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getMedia
+ */
+ public function shouldHandleMissingMedia(): void
+ {
+ $result = new AdvancedIdentityProfilePreviewResponse([]);
+
+ $this->assertNull($result->getMedia());
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/AdvancedIdentityProfileResponseTest.php b/tests/DocScan/Session/Retrieve/AdvancedIdentityProfileResponseTest.php
new file mode 100644
index 00000000..d4b446d3
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/AdvancedIdentityProfileResponseTest.php
@@ -0,0 +1,97 @@
+ 'UK_TFIDA',
+ 'schemes_compliance' => [
+ 0 => [
+ 'scheme' => [
+ 'type' => 'DBS',
+ 'objective' => 'STANDARD',
+ ],
+ 'requirements_met' => true,
+ 'requirements_not_met_info' => 'some string here',
+ ],
+ ],
+ 'media' => [
+ ],
+ ];
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getIdentityProfileReport
+ * @covers ::getFailureReason
+ * @covers ::getResult
+ * @covers ::getSubjectId
+ */
+ public function shouldCreatedCorrectly(): void
+ {
+ $testData = [
+ 'subject_id' => self::SUBJECT_ID,
+ 'result' => self::RESULT,
+ 'failure_reason' => [
+ 'reason_code' => self::REASON_CODE,
+ 'requirements_not_met_details' => [
+ 0 => [
+ 'failure_type' => self::FAILURE_TYPE,
+ 'document_type' => self::DOCUMENT_TYPE,
+ 'document_country_iso_code' => self::DOCUMENT_COUNTRY_ISO_CODE,
+ 'audit_id' => self::AUDIT_ID,
+ 'details' => self::DETAILS
+ ]
+ ]
+ ],
+ 'identity_profile_report' => self::IDENTITY_PROFILE_REPORT,
+ ];
+
+ $result = new AdvancedIdentityProfileResponse($testData);
+ $this->assertEquals(self::RESULT, $result->getResult());
+ $this->assertEquals(self::SUBJECT_ID, $result->getSubjectId());
+ $this->assertEquals((object)self::IDENTITY_PROFILE_REPORT, $result->getIdentityProfileReport());
+ $this->assertInstanceOf(FailureReasonResponse::class, $result->getFailureReason());
+ $this->assertEquals(self::REASON_CODE, $result->getFailureReason()->getReasonCode());
+ $requirementNotMetDetailsResponse = $result->getFailureReason()->getRequirementNotMetDetails();
+ $this->assertEquals(self::FAILURE_TYPE, $requirementNotMetDetailsResponse->getFailureType());
+ $this->assertEquals(self::DOCUMENT_TYPE, $requirementNotMetDetailsResponse->getDocumentType());
+ $this->assertEquals(self::AUDIT_ID, $requirementNotMetDetailsResponse->getAuditId());
+ $this->assertEquals(self::DETAILS, $requirementNotMetDetailsResponse->getDetails());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getFailureReason
+ * @covers ::getIdentityProfileReport
+ */
+ public function shouldHandleMissingOptionalFields(): void
+ {
+ $testData = [
+ 'result' => self::RESULT,
+ ];
+
+ $result = new AdvancedIdentityProfileResponse($testData);
+ $this->assertEquals(self::RESULT, $result->getResult());
+ $this->assertEquals('', $result->getSubjectId());
+ $this->assertNull($result->getFailureReason());
+ $this->assertNull($result->getIdentityProfileReport());
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/ApplicantProfileResourceResponseTest.php b/tests/DocScan/Session/Retrieve/ApplicantProfileResourceResponseTest.php
new file mode 100644
index 00000000..645deaf2
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/ApplicantProfileResourceResponseTest.php
@@ -0,0 +1,81 @@
+ self::SOME_ID,
+ 'source' => [
+ 'type' => self::SOME_SOURCE_TYPE,
+ ],
+ 'media' => [
+ 'id' => self::SOME_MEDIA_ID,
+ 'type' => self::SOME_MEDIA_TYPE,
+ 'created' => self::SOME_CREATED_AT,
+ 'last_updated' => self::SOME_LAST_UPDATED,
+ ],
+ 'created_at' => self::SOME_CREATED_AT,
+ 'last_updated' => self::SOME_LAST_UPDATED,
+ 'tasks' => [],
+ ];
+
+ $result = new ApplicantProfileResourceResponse($input);
+
+ $this->assertEquals(self::SOME_ID, $result->getId());
+ $this->assertNotNull($result->getSource());
+ $this->assertInstanceOf(MediaResponse::class, $result->getMedia());
+ $this->assertEquals(self::SOME_MEDIA_ID, $result->getMedia()->getId());
+ $this->assertEquals(self::SOME_MEDIA_TYPE, $result->getMedia()->getType());
+ $this->assertEquals(
+ DateTime::stringToDateTime(self::SOME_CREATED_AT),
+ $result->getCreatedAt()
+ );
+ $this->assertEquals(
+ DateTime::stringToDateTime(self::SOME_LAST_UPDATED),
+ $result->getLastUpdated()
+ );
+ $this->assertCount(0, $result->getTasks());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldNotThrowExceptionWhenMissingValues()
+ {
+ $result = new ApplicantProfileResourceResponse([]);
+
+ $this->assertNull($result->getId());
+ $this->assertNull($result->getMedia());
+ $this->assertNull($result->getCreatedAt());
+ $this->assertNull($result->getLastUpdated());
+ $this->assertCount(0, $result->getTasks());
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/BreakdownResponseTest.php b/tests/DocScan/Session/Retrieve/BreakdownResponseTest.php
index dc92d2fd..9e4e9a30 100644
--- a/tests/DocScan/Session/Retrieve/BreakdownResponseTest.php
+++ b/tests/DocScan/Session/Retrieve/BreakdownResponseTest.php
@@ -25,11 +25,14 @@ class BreakdownResponseTest extends TestCase
],
];
+ private const SOME_PROCESS = 'AUTOMATED';
+
/**
* @test
* @covers ::__construct
* @covers ::getSubCheck
* @covers ::getResult
+ * @covers ::getProcess
* @covers ::getDetails
* @covers \Yoti\DocScan\Session\Retrieve\DetailsResponse::__construct
* @covers \Yoti\DocScan\Session\Retrieve\DetailsResponse::getName
@@ -40,6 +43,7 @@ public function shouldBuildCorrectly()
$input = [
'sub_check' => self::SOME_SUB_CHECK,
'result' => self::SOME_RESULT,
+ 'process' => self::SOME_PROCESS,
'details' => self::SOME_DETAILS,
];
@@ -47,6 +51,7 @@ public function shouldBuildCorrectly()
$this->assertEquals(self::SOME_SUB_CHECK, $result->getSubCheck());
$this->assertEquals(self::SOME_RESULT, $result->getResult());
+ $this->assertEquals(self::SOME_PROCESS, $result->getProcess());
$details = $result->getDetails();
for ($i = 0; $i < count(self::SOME_DETAILS); $i++) {
@@ -61,6 +66,7 @@ public function shouldBuildCorrectly()
* @covers ::__construct
* @covers ::getSubCheck
* @covers ::getResult
+ * @covers ::getProcess
* @covers ::getDetails
*/
public function shouldNotThrowExceptionWhenValuesAreMissing()
@@ -71,6 +77,7 @@ public function shouldNotThrowExceptionWhenValuesAreMissing()
$this->assertNull($result->getSubCheck());
$this->assertNull($result->getResult());
+ $this->assertNull($result->getProcess());
$this->assertCount(0, $result->getDetails());
}
}
diff --git a/tests/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponseTest.php b/tests/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponseTest.php
index fe724975..7a73a6e4 100644
--- a/tests/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponseTest.php
+++ b/tests/DocScan/Session/Retrieve/Configuration/SessionConfigurationResponseTest.php
@@ -14,6 +14,8 @@ class SessionConfigurationResponseTest extends TestCase
private const SOME_CLIENT_SESSION_TTL = 12345678;
private const SOME_SESSION_ID = 'SOME_SESSION_ID';
private const SOME_REQUESTED_CHECKS = ['SOME_CHECK', 'SOME_ANOTHER_CHECK'];
+ private const SOME_SCREEN_IDENTIFIER = 'someScreenIdentifier';
+ private const ANOTHER_SCREEN_IDENTIFIER = 'anotherScreenIdentifier';
private const SOME_CAPTURE = [
'biometric_consent' => 'SOME_STRING',
'required_resources' => [
@@ -61,4 +63,82 @@ public function shouldBuildCorrectly()
$this->assertCount(2, $result->getRequestedChecks());
}
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getSdkConfig
+ * @covers ::getSuppressedScreens
+ */
+ public function shouldBuildWithSdkConfig()
+ {
+ $sdkConfig = [
+ 'primary_colour' => '#123456',
+ 'suppressed_screens' => [self::SOME_SCREEN_IDENTIFIER, self::ANOTHER_SCREEN_IDENTIFIER]
+ ];
+
+ $sessionData = [
+ 'client_session_token_ttl' => self::SOME_CLIENT_SESSION_TTL,
+ 'session_id' => self::SOME_SESSION_ID,
+ 'requested_checks' => self::SOME_REQUESTED_CHECKS,
+ 'capture' => self::SOME_CAPTURE,
+ 'sdk_config' => $sdkConfig
+ ];
+
+ $result = new SessionConfigurationResponse($sessionData);
+
+ $this->assertEquals($sdkConfig, $result->getSdkConfig());
+ $this->assertEquals(
+ [self::SOME_SCREEN_IDENTIFIER, self::ANOTHER_SCREEN_IDENTIFIER],
+ $result->getSuppressedScreens()
+ );
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getSdkConfig
+ * @covers ::getSuppressedScreens
+ */
+ public function shouldReturnNullForSdkConfigWhenNotPresent()
+ {
+ $sessionData = [
+ 'client_session_token_ttl' => self::SOME_CLIENT_SESSION_TTL,
+ 'session_id' => self::SOME_SESSION_ID,
+ 'requested_checks' => self::SOME_REQUESTED_CHECKS,
+ 'capture' => self::SOME_CAPTURE
+ ];
+
+ $result = new SessionConfigurationResponse($sessionData);
+
+ $this->assertNull($result->getSdkConfig());
+ $this->assertNull($result->getSuppressedScreens());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getSdkConfig
+ * @covers ::getSuppressedScreens
+ */
+ public function shouldReturnNullForSuppressedScreensWhenNotInSdkConfig()
+ {
+ $sdkConfig = [
+ 'primary_colour' => '#123456'
+ // No suppressed_screens
+ ];
+
+ $sessionData = [
+ 'client_session_token_ttl' => self::SOME_CLIENT_SESSION_TTL,
+ 'session_id' => self::SOME_SESSION_ID,
+ 'requested_checks' => self::SOME_REQUESTED_CHECKS,
+ 'capture' => self::SOME_CAPTURE,
+ 'sdk_config' => $sdkConfig
+ ];
+
+ $result = new SessionConfigurationResponse($sessionData);
+
+ $this->assertEquals($sdkConfig, $result->getSdkConfig());
+ $this->assertNull($result->getSuppressedScreens());
+ }
}
diff --git a/tests/DocScan/Session/Retrieve/GetSessionResultTest.php b/tests/DocScan/Session/Retrieve/GetSessionResultTest.php
index 0d879a12..ee30dddb 100644
--- a/tests/DocScan/Session/Retrieve/GetSessionResultTest.php
+++ b/tests/DocScan/Session/Retrieve/GetSessionResultTest.php
@@ -4,6 +4,8 @@
namespace Yoti\Test\DocScan\Session\Retrieve;
+use Yoti\DocScan\Session\Retrieve\AdvancedIdentityProfilePreviewResponse;
+use Yoti\DocScan\Session\Retrieve\AdvancedIdentityProfileResponse;
use Yoti\DocScan\Session\Retrieve\AuthenticityCheckResponse;
use Yoti\DocScan\Session\Retrieve\CheckResponse;
use Yoti\DocScan\Session\Retrieve\GetSessionResult;
@@ -296,4 +298,60 @@ public function shouldParseImportTokenResponse()
$this->assertInstanceOf(MediaResponse::class, $result->getImportToken()->getMedia());
$this->assertEquals('SOME_REASON', $result->getImportToken()->getFailureReason());
}
+
+ /**
+ * @test
+ * @covers ::getAdvancedIdentityProfile
+ * @covers ::__construct
+ */
+ public function shouldParseAdvancedIdentityProfileResponse()
+ {
+ $input = [
+ 'advanced_identity_profile' => self::IDENTITY_PROFILE,
+ ];
+
+ $result = new GetSessionResult($input);
+
+ $this->assertInstanceOf(AdvancedIdentityProfileResponse::class, $result->getAdvancedIdentityProfile());
+ }
+
+ /**
+ * @test
+ * @covers ::getAdvancedIdentityProfilePreview
+ * @covers ::__construct
+ */
+ public function shouldParseAdvancedIdentityProfilePreviewResponse()
+ {
+ $input = [
+ 'advanced_identity_profile_preview' => [
+ 'media' => [
+ 'id' => 'SOME_ID',
+ 'type' => 'JSON',
+ 'created' => '2021-06-11T11:39:24Z',
+ 'last_updated' => '2021-06-11T11:39:24Z',
+ ]
+ ],
+ ];
+
+ $result = new GetSessionResult($input);
+
+ $this->assertInstanceOf(
+ AdvancedIdentityProfilePreviewResponse::class,
+ $result->getAdvancedIdentityProfilePreview()
+ );
+ }
+
+ /**
+ * @test
+ * @covers ::getAdvancedIdentityProfile
+ * @covers ::getAdvancedIdentityProfilePreview
+ * @covers ::__construct
+ */
+ public function shouldReturnNullWhenAdvancedIdentityProfileNotPresent()
+ {
+ $result = new GetSessionResult([]);
+
+ $this->assertNull($result->getAdvancedIdentityProfile());
+ $this->assertNull($result->getAdvancedIdentityProfilePreview());
+ }
}
diff --git a/tests/DocScan/Session/Retrieve/PageResponseTest.php b/tests/DocScan/Session/Retrieve/PageResponseTest.php
index 91306f12..89527f10 100644
--- a/tests/DocScan/Session/Retrieve/PageResponseTest.php
+++ b/tests/DocScan/Session/Retrieve/PageResponseTest.php
@@ -56,6 +56,25 @@ public function testGetFrames()
$this->containsOnlyInstancesOf(FrameResponse::class, $pageResponse->getFrames());
}
+ /**
+ * @covers ::__construct
+ * @covers ::getExtractionImageIds
+ */
+ public function testGetExtractionImageIds()
+ {
+ $extractionImageIds = [
+ '066a9372-0a52-4fe4-a026-866f8aee6fcb',
+ '9b0c9c0a-ff30-41ed-815b-d95d63271d45',
+ ];
+
+ $pageResponse = new PageResponse([
+ 'extraction_image_ids' => $extractionImageIds,
+ ]);
+
+ $this->assertCount(2, $pageResponse->getExtractionImageIds());
+ $this->assertEquals($extractionImageIds, $pageResponse->getExtractionImageIds());
+ }
+
/**
* @test
* @covers ::__construct
@@ -67,5 +86,6 @@ public function shouldNotThrowExceptionWhenMissingValues()
$this->assertNull($result->getCaptureMethod());
$this->assertNull($result->getMedia());
$this->assertEquals([], $result->getFrames());
+ $this->assertEquals([], $result->getExtractionImageIds());
}
}
diff --git a/tests/DocScan/Session/Retrieve/ResourceContainerTest.php b/tests/DocScan/Session/Retrieve/ResourceContainerTest.php
index dbc8c2c3..f73beb81 100644
--- a/tests/DocScan/Session/Retrieve/ResourceContainerTest.php
+++ b/tests/DocScan/Session/Retrieve/ResourceContainerTest.php
@@ -4,7 +4,9 @@
namespace Yoti\Test\DocScan\Session\Retrieve;
+use Yoti\DocScan\Session\Retrieve\ApplicantProfileResourceResponse;
use Yoti\DocScan\Session\Retrieve\ResourceContainer;
+use Yoti\DocScan\Session\Retrieve\ShareCodeResourceResponse;
use Yoti\DocScan\Session\Retrieve\StaticLivenessResourceResponse;
use Yoti\DocScan\Session\Retrieve\ZoomLivenessResourceResponse;
use Yoti\Test\TestCase;
@@ -27,6 +29,8 @@ class ResourceContainerTest extends TestCase
* @covers ::parseFaceCapture
* @covers ::parseSupplementaryDocuments
* @covers ::getSupplementaryDocuments
+ * @covers ::parseShareCodes
+ * @covers ::getShareCodes
*/
public function shouldBuildCorrectly()
{
@@ -46,7 +50,14 @@ public function shouldBuildCorrectly()
],
'face_capture' => [
['id' => 'SOME_ID']
- ]
+ ],
+ 'share_codes' => [
+ ['id' => 'share-code-1'],
+ ['id' => 'share-code-2'],
+ ],
+ 'applicant_profiles' => [
+ ['id' => 'applicant-profile-1'],
+ ],
];
$result = new ResourceContainer($input);
@@ -57,6 +68,8 @@ public function shouldBuildCorrectly()
$this->assertCount(1, $result->getStaticLivenessResources());
$this->assertCount(2, $result->getSupplementaryDocuments());
$this->assertCount(1, $result->getFaceCapture());
+ $this->assertCount(2, $result->getShareCodes());
+ $this->assertCount(1, $result->getApplicantProfiles());
}
/**
@@ -69,6 +82,8 @@ public function shouldNotThrowExceptionWhenMissingValues()
$this->assertCount(0, $result->getIdDocuments());
$this->assertCount(0, $result->getLivenessCapture());
+ $this->assertCount(0, $result->getShareCodes());
+ $this->assertCount(0, $result->getApplicantProfiles());
}
/**
@@ -129,4 +144,79 @@ public function shouldFilterZoomLivenessResources(): void
$this->assertCount(3, $result->getLivenessCapture());
$this->assertCount(2, $result->getZoomLivenessResources());
}
+
+ /**
+ * @test
+ * @covers ::parseShareCodes
+ * @covers ::getShareCodes
+ */
+ public function shouldParseShareCodes(): void
+ {
+ $input = [
+ 'share_codes' => [
+ [
+ 'id' => 'share-code-1',
+ 'source' => ['type' => 'END_USER'],
+ 'created_at' => '2026-01-14T10:00:00Z',
+ 'last_updated' => '2026-01-14T11:00:00Z',
+ 'tasks' => [],
+ ],
+ [
+ 'id' => 'share-code-2',
+ 'source' => ['type' => 'END_USER'],
+ 'created_at' => '2026-01-14T12:00:00Z',
+ 'last_updated' => '2026-01-14T13:00:00Z',
+ 'tasks' => [],
+ ],
+ ],
+ ];
+
+ $result = new ResourceContainer($input);
+
+ $this->assertCount(2, $result->getShareCodes());
+ $this->assertContainsOnlyInstancesOf(
+ ShareCodeResourceResponse::class,
+ $result->getShareCodes()
+ );
+ $this->assertEquals('share-code-1', $result->getShareCodes()[0]->getId());
+ $this->assertEquals('share-code-2', $result->getShareCodes()[1]->getId());
+ }
+
+ /**
+ * @test
+ * @covers ::parseApplicantProfiles
+ * @covers ::getApplicantProfiles
+ */
+ public function shouldParseApplicantProfiles(): void
+ {
+ $input = [
+ 'applicant_profiles' => [
+ [
+ 'id' => '3fa85f64-5717-4562-b3fc-2c963f66afa6',
+ 'source' => ['type' => 'END_USER'],
+ 'media' => [
+ 'id' => 'media-id-123',
+ 'type' => 'IMAGE',
+ 'created' => '2021-06-11T11:39:24Z',
+ 'last_updated' => '2021-06-11T11:39:24Z',
+ ],
+ 'created_at' => '2021-06-11T11:39:24Z',
+ 'last_updated' => '2021-06-11T11:39:24Z',
+ 'tasks' => [],
+ ],
+ ],
+ ];
+
+ $result = new ResourceContainer($input);
+
+ $this->assertCount(1, $result->getApplicantProfiles());
+ $this->assertContainsOnlyInstancesOf(
+ ApplicantProfileResourceResponse::class,
+ $result->getApplicantProfiles()
+ );
+ $this->assertEquals(
+ '3fa85f64-5717-4562-b3fc-2c963f66afa6',
+ $result->getApplicantProfiles()[0]->getId()
+ );
+ }
}
diff --git a/tests/DocScan/Session/Retrieve/ShareCodeMediaResponseTest.php b/tests/DocScan/Session/Retrieve/ShareCodeMediaResponseTest.php
new file mode 100644
index 00000000..a6b5fa3b
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/ShareCodeMediaResponseTest.php
@@ -0,0 +1,70 @@
+ [
+ 'id' => 'some-media-id',
+ 'type' => 'IMAGE',
+ 'created' => '2026-01-14T10:00:00Z',
+ 'last_updated' => '2026-01-14T11:00:00Z',
+ ],
+ ];
+
+ $result = new ShareCodeMediaResponse($input);
+
+ $this->assertInstanceOf(MediaResponse::class, $result->getMedia());
+ $this->assertEquals('some-media-id', $result->getMedia()->getId());
+ $this->assertEquals('IMAGE', $result->getMedia()->getType());
+ $this->assertNotNull($result->getMedia()->getCreated());
+ $this->assertNotNull($result->getMedia()->getLastUpdated());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getMedia
+ */
+ public function shouldHandleMissingMedia()
+ {
+ $result = new ShareCodeMediaResponse([]);
+
+ $this->assertNull($result->getMedia());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getMedia
+ */
+ public function shouldHandleEmptyMediaObject()
+ {
+ $input = [
+ 'media' => [],
+ ];
+
+ $result = new ShareCodeMediaResponse($input);
+
+ $this->assertInstanceOf(MediaResponse::class, $result->getMedia());
+ $this->assertNull($result->getMedia()->getId());
+ $this->assertNull($result->getMedia()->getType());
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/ShareCodeResourceResponseTest.php b/tests/DocScan/Session/Retrieve/ShareCodeResourceResponseTest.php
new file mode 100644
index 00000000..d4528ed0
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/ShareCodeResourceResponseTest.php
@@ -0,0 +1,327 @@
+ 'share-code-123',
+ 'source' => ['type' => 'END_USER'],
+ 'created_at' => '2026-01-14T10:00:00Z',
+ 'last_updated' => '2026-01-14T11:00:00Z',
+ 'lookup_profile' => [
+ 'media' => ['id' => 'media-1', 'type' => 'JSON'],
+ ],
+ 'returned_profile' => [
+ 'media' => ['id' => 'media-2', 'type' => 'JSON'],
+ ],
+ 'id_photo' => [
+ 'media' => ['id' => 'media-3', 'type' => 'IMAGE'],
+ ],
+ 'file' => [
+ 'media' => ['id' => 'media-4', 'type' => 'PDF'],
+ ],
+ 'tasks' => [
+ [
+ 'type' => self::VERIFY_SHARE_CODE_TASK,
+ 'id' => 'task-123',
+ 'state' => 'DONE',
+ 'created' => '2026-01-14T10:00:00Z',
+ 'last_updated' => '2026-01-14T11:00:00Z',
+ 'generated_media' => [
+ ['id' => 'gm-1', 'type' => 'PDF'],
+ ['id' => 'gm-2', 'type' => 'IMAGE'],
+ ],
+ ],
+ ],
+ ];
+
+ $result = new ShareCodeResourceResponse($input);
+
+ $this->assertEquals('share-code-123', $result->getId());
+ $this->assertInstanceOf(\DateTime::class, $result->getCreatedAt());
+ $this->assertInstanceOf(\DateTime::class, $result->getLastUpdated());
+
+ $this->assertInstanceOf(ShareCodeMediaResponse::class, $result->getLookupProfile());
+ $this->assertInstanceOf(MediaResponse::class, $result->getLookupProfile()->getMedia());
+ $this->assertEquals('media-1', $result->getLookupProfile()->getMedia()->getId());
+ $this->assertEquals('JSON', $result->getLookupProfile()->getMedia()->getType());
+
+ $this->assertInstanceOf(ShareCodeMediaResponse::class, $result->getReturnedProfile());
+ $this->assertEquals('media-2', $result->getReturnedProfile()->getMedia()->getId());
+
+ $this->assertInstanceOf(ShareCodeMediaResponse::class, $result->getIdPhoto());
+ $this->assertEquals('media-3', $result->getIdPhoto()->getMedia()->getId());
+ $this->assertEquals('IMAGE', $result->getIdPhoto()->getMedia()->getType());
+
+ $this->assertInstanceOf(ShareCodeMediaResponse::class, $result->getFile());
+ $this->assertEquals('media-4', $result->getFile()->getMedia()->getId());
+ $this->assertEquals('PDF', $result->getFile()->getMedia()->getType());
+
+ $this->assertCount(1, $result->getVerifyShareCodeTasks());
+ $this->assertContainsOnlyInstancesOf(
+ VerifyShareCodeTaskResponse::class,
+ $result->getVerifyShareCodeTasks()
+ );
+ $this->assertEquals('task-123', $result->getVerifyShareCodeTasks()[0]->getId());
+ $this->assertEquals('DONE', $result->getVerifyShareCodeTasks()[0]->getState());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldNotThrowExceptionWhenMissingValues()
+ {
+ $result = new ShareCodeResourceResponse([]);
+
+ $this->assertNull($result->getId());
+ $this->assertNull($result->getCreatedAt());
+ $this->assertNull($result->getLastUpdated());
+ $this->assertNull($result->getLookupProfile());
+ $this->assertNull($result->getReturnedProfile());
+ $this->assertNull($result->getIdPhoto());
+ $this->assertNull($result->getFile());
+ $this->assertCount(0, $result->getVerifyShareCodeTasks());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getVerifyShareCodeTasks
+ */
+ public function shouldFilterVerifyShareCodeTasks()
+ {
+ $input = [
+ 'id' => 'share-code-mixed',
+ 'tasks' => [
+ [
+ 'type' => self::VERIFY_SHARE_CODE_TASK,
+ 'id' => 'task-verify',
+ 'state' => 'DONE',
+ ],
+ [
+ 'type' => 'OTHER_TASK_TYPE',
+ 'id' => 'task-other',
+ 'state' => 'PENDING',
+ ],
+ ],
+ ];
+
+ $result = new ShareCodeResourceResponse($input);
+
+ $this->assertCount(2, $result->getTasks());
+ $this->assertCount(1, $result->getVerifyShareCodeTasks());
+ $this->assertEquals('task-verify', $result->getVerifyShareCodeTasks()[0]->getId());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getVerifyShareCodeTasks
+ */
+ public function shouldHandleMultipleVerifyShareCodeTasks()
+ {
+ $input = [
+ 'id' => 'share-code-multi',
+ 'tasks' => [
+ [
+ 'type' => self::VERIFY_SHARE_CODE_TASK,
+ 'id' => 'task-1',
+ 'state' => 'PENDING',
+ ],
+ [
+ 'type' => self::VERIFY_SHARE_CODE_TASK,
+ 'id' => 'task-2',
+ 'state' => 'DONE',
+ ],
+ ],
+ ];
+
+ $result = new ShareCodeResourceResponse($input);
+
+ $this->assertCount(2, $result->getVerifyShareCodeTasks());
+ $this->assertEquals('task-1', $result->getVerifyShareCodeTasks()[0]->getId());
+ $this->assertEquals('task-2', $result->getVerifyShareCodeTasks()[1]->getId());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getVerifyShareCodeTasks
+ */
+ public function shouldHandleNoTasks()
+ {
+ $input = [
+ 'id' => 'share-code-no-tasks',
+ 'tasks' => [],
+ ];
+
+ $result = new ShareCodeResourceResponse($input);
+
+ $this->assertCount(0, $result->getVerifyShareCodeTasks());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getLookupProfile
+ * @covers ::getReturnedProfile
+ * @covers ::getIdPhoto
+ * @covers ::getFile
+ */
+ public function shouldHandlePartialMediaFields()
+ {
+ $input = [
+ 'id' => 'share-code-partial',
+ 'lookup_profile' => [
+ 'media' => ['id' => 'media-1', 'type' => 'JSON'],
+ ],
+ ];
+
+ $result = new ShareCodeResourceResponse($input);
+
+ $this->assertInstanceOf(ShareCodeMediaResponse::class, $result->getLookupProfile());
+ $this->assertEquals('media-1', $result->getLookupProfile()->getMedia()->getId());
+ $this->assertNull($result->getReturnedProfile());
+ $this->assertNull($result->getIdPhoto());
+ $this->assertNull($result->getFile());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldHandleEmptyMediaObjects()
+ {
+ $input = [
+ 'id' => 'share-code-empty-media',
+ 'id_photo' => [],
+ ];
+
+ $result = new ShareCodeResourceResponse($input);
+
+ $this->assertInstanceOf(ShareCodeMediaResponse::class, $result->getIdPhoto());
+ $this->assertNull($result->getIdPhoto()->getMedia());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getCreatedAt
+ * @covers ::getLastUpdated
+ * @covers ::getLookupProfile
+ * @covers ::getReturnedProfile
+ * @covers ::getIdPhoto
+ * @covers ::getFile
+ * @covers ::getVerifyShareCodeTasks
+ */
+ public function shouldHandleFullRealisticPayload()
+ {
+ $input = [
+ 'id' => 'abc12345-6789-abcd-ef01-234567890abc',
+ 'source' => ['type' => 'END_USER'],
+ 'created_at' => '2026-02-05T11:33:46Z',
+ 'last_updated' => '2026-02-05T11:33:50Z',
+ 'lookup_profile' => [
+ 'media' => [
+ 'id' => 'df419a66-0449-41cf-a795-6dfaa993d1f6',
+ 'type' => 'JSON',
+ 'created' => '2026-02-05T11:33:46Z',
+ 'last_updated' => '2026-02-05T11:33:50Z',
+ ],
+ ],
+ 'returned_profile' => [
+ 'media' => [
+ 'id' => 'f2152059-2868-47c9-8f5f-64966c1b66b0',
+ 'type' => 'JSON',
+ 'created' => '2026-02-05T11:33:46Z',
+ 'last_updated' => '2026-02-05T11:33:50Z',
+ ],
+ ],
+ 'id_photo' => [
+ 'media' => [
+ 'id' => '45e4ee9d-a77b-4007-afe9-ab7067687aff',
+ 'type' => 'IMAGE',
+ 'created' => '2026-02-05T11:33:46Z',
+ 'last_updated' => '2026-02-05T11:33:50Z',
+ ],
+ ],
+ 'file' => [
+ 'media' => [
+ 'id' => 'c83a9f12-1234-5678-9abc-def012345678',
+ 'type' => 'PDF',
+ 'created' => '2026-02-05T11:33:46Z',
+ 'last_updated' => '2026-02-05T11:33:50Z',
+ ],
+ ],
+ 'tasks' => [
+ [
+ 'type' => self::VERIFY_SHARE_CODE_TASK,
+ 'id' => '73141aa9-a01f-4de9-9281-1b11cda7ab75',
+ 'state' => 'DONE',
+ 'created' => '2026-02-05T11:33:46Z',
+ 'last_updated' => '2026-02-05T11:33:50Z',
+ 'generated_media' => [
+ ['id' => 'df419a66-0449-41cf-a795-6dfaa993d1f6', 'type' => 'PDF'],
+ ['id' => '45e4ee9d-a77b-4007-afe9-ab7067687aff', 'type' => 'IMAGE'],
+ ['id' => 'f2152059-2868-47c9-8f5f-64966c1b66b0', 'type' => 'JSON'],
+ ],
+ ],
+ ],
+ ];
+
+ $result = new ShareCodeResourceResponse($input);
+
+ $this->assertEquals('abc12345-6789-abcd-ef01-234567890abc', $result->getId());
+ $this->assertInstanceOf(\DateTime::class, $result->getCreatedAt());
+ $this->assertInstanceOf(\DateTime::class, $result->getLastUpdated());
+
+ $this->assertNotNull($result->getLookupProfile());
+ $this->assertEquals('df419a66-0449-41cf-a795-6dfaa993d1f6', $result->getLookupProfile()->getMedia()->getId());
+ $this->assertEquals('JSON', $result->getLookupProfile()->getMedia()->getType());
+ $this->assertNotNull($result->getLookupProfile()->getMedia()->getCreated());
+ $this->assertNotNull($result->getLookupProfile()->getMedia()->getLastUpdated());
+
+ $this->assertNotNull($result->getReturnedProfile());
+ $this->assertEquals('f2152059-2868-47c9-8f5f-64966c1b66b0', $result->getReturnedProfile()->getMedia()->getId());
+
+ $this->assertNotNull($result->getIdPhoto());
+ $this->assertEquals('45e4ee9d-a77b-4007-afe9-ab7067687aff', $result->getIdPhoto()->getMedia()->getId());
+ $this->assertEquals('IMAGE', $result->getIdPhoto()->getMedia()->getType());
+
+ $this->assertNotNull($result->getFile());
+ $this->assertEquals('c83a9f12-1234-5678-9abc-def012345678', $result->getFile()->getMedia()->getId());
+ $this->assertEquals('PDF', $result->getFile()->getMedia()->getType());
+
+ $this->assertCount(1, $result->getVerifyShareCodeTasks());
+ $this->assertEquals('DONE', $result->getVerifyShareCodeTasks()[0]->getState());
+ $this->assertCount(3, $result->getVerifyShareCodeTasks()[0]->getGeneratedMedia());
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/TaskRecommendationReasonResponseTest.php b/tests/DocScan/Session/Retrieve/TaskRecommendationReasonResponseTest.php
new file mode 100644
index 00000000..80c0a74c
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/TaskRecommendationReasonResponseTest.php
@@ -0,0 +1,48 @@
+ self::SOME_VALUE,
+ 'detail' => self::SOME_DETAIL,
+ ];
+
+ $result = new TaskRecommendationReasonResponse($input);
+
+ $this->assertEquals(self::SOME_VALUE, $result->getValue());
+ $this->assertEquals(self::SOME_DETAIL, $result->getDetail());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldNotThrowExceptionWhenMissingValues()
+ {
+ $result = new TaskRecommendationReasonResponse([]);
+
+ $this->assertNull($result->getValue());
+ $this->assertNull($result->getDetail());
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/TaskRecommendationResponseTest.php b/tests/DocScan/Session/Retrieve/TaskRecommendationResponseTest.php
new file mode 100644
index 00000000..46c265b9
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/TaskRecommendationResponseTest.php
@@ -0,0 +1,55 @@
+ self::SOME_VALUE,
+ 'reason' => [
+ 'value' => self::SOME_REASON_VALUE,
+ 'detail' => self::SOME_REASON_DETAIL,
+ ],
+ ];
+
+ $result = new TaskRecommendationResponse($input);
+
+ $this->assertEquals(self::SOME_VALUE, $result->getValue());
+ $this->assertInstanceOf(TaskRecommendationReasonResponse::class, $result->getReason());
+ $this->assertEquals(self::SOME_REASON_VALUE, $result->getReason()->getValue());
+ $this->assertEquals(self::SOME_REASON_DETAIL, $result->getReason()->getDetail());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldNotThrowExceptionWhenMissingValues()
+ {
+ $result = new TaskRecommendationResponse([]);
+
+ $this->assertNull($result->getValue());
+ $this->assertNull($result->getReason());
+ }
+}
diff --git a/tests/DocScan/Session/Retrieve/TaskResponseTest.php b/tests/DocScan/Session/Retrieve/TaskResponseTest.php
index 2b63366b..b3483119 100644
--- a/tests/DocScan/Session/Retrieve/TaskResponseTest.php
+++ b/tests/DocScan/Session/Retrieve/TaskResponseTest.php
@@ -7,6 +7,8 @@
use Yoti\DocScan\Session\Retrieve\GeneratedCheckResponse;
use Yoti\DocScan\Session\Retrieve\GeneratedMedia;
use Yoti\DocScan\Session\Retrieve\GeneratedTextDataCheckResponse;
+use Yoti\DocScan\Session\Retrieve\TaskRecommendationReasonResponse;
+use Yoti\DocScan\Session\Retrieve\TaskRecommendationResponse;
use Yoti\DocScan\Session\Retrieve\TaskResponse;
use Yoti\Test\TestCase;
use Yoti\Util\DateTime;
@@ -25,6 +27,9 @@ class TaskResponseTest extends TestCase
private const SOME_UNKNOWN_TYPE = 'someUnknownType';
private const ID_DOCUMENT_TEXT_DATA_CHECK = 'ID_DOCUMENT_TEXT_DATA_CHECK';
private const SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK = 'SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK';
+ private const SOME_RECOMMENDATION_VALUE = 'MUST_TRY_AGAIN';
+ private const SOME_RECOMMENDATION_REASON_VALUE = 'USER_ERROR';
+ private const SOME_RECOMMENDATION_REASON_DETAIL = 'NO_DOCUMENT';
/**
* @var TaskResponse
@@ -57,6 +62,13 @@ public function setup(): void
[],
[],
],
+ 'recommendation' => [
+ 'value' => self::SOME_RECOMMENDATION_VALUE,
+ 'reason' => [
+ 'value' => self::SOME_RECOMMENDATION_REASON_VALUE,
+ 'detail' => self::SOME_RECOMMENDATION_REASON_DETAIL,
+ ],
+ ],
]);
}
@@ -185,5 +197,22 @@ public function shouldNotThrowExceptionWhenAllMissingValuesExceptType()
$this->assertNull($result->getLastUpdated());
$this->assertCount(0, $result->getGeneratedChecks());
$this->assertCount(0, $result->getGeneratedMedia());
+ $this->assertNull($result->getRecommendation());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::getRecommendation
+ */
+ public function shouldReturnRecommendation()
+ {
+ $recommendation = $this->taskResponse->getRecommendation();
+
+ $this->assertInstanceOf(TaskRecommendationResponse::class, $recommendation);
+ $this->assertEquals(self::SOME_RECOMMENDATION_VALUE, $recommendation->getValue());
+ $this->assertInstanceOf(TaskRecommendationReasonResponse::class, $recommendation->getReason());
+ $this->assertEquals(self::SOME_RECOMMENDATION_REASON_VALUE, $recommendation->getReason()->getValue());
+ $this->assertEquals(self::SOME_RECOMMENDATION_REASON_DETAIL, $recommendation->getReason()->getDetail());
}
}
diff --git a/tests/DocScan/Session/Retrieve/VerifyShareCodeTaskResponseTest.php b/tests/DocScan/Session/Retrieve/VerifyShareCodeTaskResponseTest.php
new file mode 100644
index 00000000..b7d4dbca
--- /dev/null
+++ b/tests/DocScan/Session/Retrieve/VerifyShareCodeTaskResponseTest.php
@@ -0,0 +1,62 @@
+ self::VERIFY_SHARE_CODE_TASK,
+ 'id' => 'some-task-id',
+ 'state' => 'DONE',
+ 'created' => '2026-01-14T10:00:00Z',
+ 'last_updated' => '2026-01-14T11:00:00Z',
+ 'generated_media' => [
+ ['id' => 'media-1', 'type' => 'PDF'],
+ ['id' => 'media-2', 'type' => 'IMAGE'],
+ ],
+ ];
+
+ $result = new VerifyShareCodeTaskResponse($input);
+
+ $this->assertInstanceOf(TaskResponse::class, $result);
+ $this->assertEquals(self::VERIFY_SHARE_CODE_TASK, $result->getType());
+ $this->assertEquals('some-task-id', $result->getId());
+ $this->assertEquals('DONE', $result->getState());
+ $this->assertNotNull($result->getCreated());
+ $this->assertNotNull($result->getLastUpdated());
+ $this->assertCount(2, $result->getGeneratedMedia());
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldNotThrowExceptionWhenMissingValues()
+ {
+ $result = new VerifyShareCodeTaskResponse([]);
+
+ $this->assertNull($result->getType());
+ $this->assertNull($result->getId());
+ $this->assertNull($result->getState());
+ $this->assertNull($result->getCreated());
+ $this->assertNull($result->getLastUpdated());
+ $this->assertCount(0, $result->getGeneratedMedia());
+ }
+}
diff --git a/tests/Http/AuthStrategy/BearerTokenStrategyTest.php b/tests/Http/AuthStrategy/BearerTokenStrategyTest.php
new file mode 100644
index 00000000..6231f583
--- /dev/null
+++ b/tests/Http/AuthStrategy/BearerTokenStrategyTest.php
@@ -0,0 +1,68 @@
+createAuthHeaders(self::SOME_HTTP_METHOD, self::SOME_ENDPOINT, null);
+
+ $this->assertArrayHasKey('Authorization', $headers);
+ $this->assertEquals('Bearer ' . self::SOME_TOKEN, $headers['Authorization']);
+ }
+
+ /**
+ * @test
+ * @covers ::createQueryParams
+ */
+ public function shouldReturnEmptyQueryParams()
+ {
+ $strategy = new BearerTokenStrategy(self::SOME_TOKEN);
+ $params = $strategy->createQueryParams();
+
+ $this->assertIsArray($params);
+ $this->assertEmpty($params);
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ */
+ public function shouldThrowOnEmptyToken()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ new BearerTokenStrategy('');
+ }
+
+ /**
+ * @test
+ * @covers ::createAuthHeaders
+ */
+ public function shouldReturnOnlyAuthorizationHeader()
+ {
+ $strategy = new BearerTokenStrategy(self::SOME_TOKEN);
+ $headers = $strategy->createAuthHeaders('POST', '/endpoint', null);
+
+ $this->assertCount(1, $headers);
+ $this->assertArrayHasKey('Authorization', $headers);
+ }
+}
diff --git a/tests/Http/AuthStrategy/NoAuthStrategyTest.php b/tests/Http/AuthStrategy/NoAuthStrategyTest.php
new file mode 100644
index 00000000..e671d467
--- /dev/null
+++ b/tests/Http/AuthStrategy/NoAuthStrategyTest.php
@@ -0,0 +1,40 @@
+createAuthHeaders('GET', '/endpoint', null);
+
+ $this->assertIsArray($headers);
+ $this->assertEmpty($headers);
+ }
+
+ /**
+ * @test
+ * @covers ::createQueryParams
+ */
+ public function shouldReturnEmptyQueryParams()
+ {
+ $strategy = new NoAuthStrategy();
+ $params = $strategy->createQueryParams();
+
+ $this->assertIsArray($params);
+ $this->assertEmpty($params);
+ }
+}
diff --git a/tests/Http/AuthStrategy/SignedRequestStrategyTest.php b/tests/Http/AuthStrategy/SignedRequestStrategyTest.php
new file mode 100644
index 00000000..1d44b54b
--- /dev/null
+++ b/tests/Http/AuthStrategy/SignedRequestStrategyTest.php
@@ -0,0 +1,133 @@
+createAuthHeaders(self::SOME_HTTP_METHOD, self::SOME_ENDPOINT, null);
+
+ $this->assertArrayHasKey('X-Yoti-Auth-Digest', $headers);
+ $this->assertNotEmpty($headers['X-Yoti-Auth-Digest']);
+ }
+
+ /**
+ * @test
+ * @covers ::createAuthHeaders
+ */
+ public function shouldReturnDigestHeaderWithPayload()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+ $strategy = new SignedRequestStrategy($pemFile);
+
+ $payload = Payload::fromString('some payload content');
+ $headers = $strategy->createAuthHeaders('POST', self::SOME_ENDPOINT, $payload);
+
+ $this->assertArrayHasKey('X-Yoti-Auth-Digest', $headers);
+ $this->assertNotEmpty($headers['X-Yoti-Auth-Digest']);
+ }
+
+ /**
+ * @test
+ * @covers ::createQueryParams
+ */
+ public function shouldReturnNonceAndTimestampQueryParams()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+ $strategy = new SignedRequestStrategy($pemFile);
+
+ $params = $strategy->createQueryParams();
+
+ $this->assertArrayHasKey('nonce', $params);
+ $this->assertArrayHasKey('timestamp', $params);
+ $this->assertNotEmpty($params['nonce']);
+ $this->assertNotEmpty($params['timestamp']);
+ }
+
+ /**
+ * @test
+ * @covers ::createQueryParams
+ */
+ public function shouldIncludeNonceAsUuidFormat()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+ $strategy = new SignedRequestStrategy($pemFile);
+
+ $params = $strategy->createQueryParams();
+
+ // UUID v4 pattern: 8-4-4-4-12 hex chars
+ $this->assertMatchesRegularExpression(
+ '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/',
+ $params['nonce']
+ );
+ }
+
+ /**
+ * @test
+ * @covers ::__construct
+ * @covers ::createQueryParams
+ */
+ public function shouldIncludeSdkIdWhenProvided()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+ $strategy = new SignedRequestStrategy($pemFile, self::SOME_SDK_ID);
+
+ $params = $strategy->createQueryParams();
+
+ $this->assertArrayHasKey('sdkId', $params);
+ $this->assertEquals(self::SOME_SDK_ID, $params['sdkId']);
+ }
+
+ /**
+ * @test
+ * @covers ::createQueryParams
+ */
+ public function shouldNotIncludeSdkIdWhenNotProvided()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+ $strategy = new SignedRequestStrategy($pemFile);
+
+ $params = $strategy->createQueryParams();
+
+ $this->assertArrayNotHasKey('sdkId', $params);
+ }
+
+ /**
+ * @test
+ * @covers ::createQueryParams
+ */
+ public function shouldReturnDifferentNonceEachTime()
+ {
+ $pemFile = PemFile::fromFilePath(TestData::PEM_FILE);
+ $strategy = new SignedRequestStrategy($pemFile);
+
+ $params1 = $strategy->createQueryParams();
+ $params2 = $strategy->createQueryParams();
+
+ $this->assertNotEquals($params1['nonce'], $params2['nonce']);
+ }
+}
diff --git a/tests/Http/RequestBuilderTest.php b/tests/Http/RequestBuilderTest.php
index 75558344..a8b4e9f0 100644
--- a/tests/Http/RequestBuilderTest.php
+++ b/tests/Http/RequestBuilderTest.php
@@ -336,10 +336,13 @@ public function testBuildWithoutBaseUrl()
public function testBuildWithoutPem()
{
$this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Pem file must be provided to Yoti\\Http\\RequestBuilder');
+ $this->expectExceptionMessage(
+ 'Either an AuthStrategy or a PEM file must be provided to Yoti\\Http\\RequestBuilder'
+ );
(new RequestBuilder())
->withBaseUrl(self::SOME_BASE_URL)
+ ->withGet()
->build();
}
diff --git a/tests/Identity/Policy/PolicyBuilderTest.php b/tests/Identity/Policy/PolicyBuilderTest.php
index 58b7968e..4865504b 100644
--- a/tests/Identity/Policy/PolicyBuilderTest.php
+++ b/tests/Identity/Policy/PolicyBuilderTest.php
@@ -680,7 +680,7 @@ public function testWithAdvancedIdentityProfileRequirements()
"label" => "identity-AL-L1",
"type" => "IDENTITY",
- "objective"=> "AL_L1"
+ "objective" => "AL_L1"
],
[
"label" => "identity-AL-M1",
diff --git a/tests/Profile/BaseProfileTest.php b/tests/Profile/BaseProfileTest.php
index 71fce4bb..20c7b122 100644
--- a/tests/Profile/BaseProfileTest.php
+++ b/tests/Profile/BaseProfileTest.php
@@ -138,7 +138,7 @@ public function testGetAttributeById()
$givenNamesAttribute = new ProtobufAttribute([
'name' => self::SOME_ATTRIBUTE,
- 'value' => utf8_decode('Alan'),
+ 'value' => mb_convert_encoding('Alan', 'ISO-8859-1', 'UTF-8'),
'content_type' => self::CONTENT_TYPE_STRING,
]);
$newAttribute = AttributeConverter::convertToYotiAttribute($givenNamesAttribute);
diff --git a/tests/Profile/Util/EncryptedDataTest.php b/tests/Profile/Util/EncryptedDataTest.php
index 1680c9a8..4d1e8741 100644
--- a/tests/Profile/Util/EncryptedDataTest.php
+++ b/tests/Profile/Util/EncryptedDataTest.php
@@ -28,6 +28,11 @@ class EncrypedDataTest extends TestCase
*/
private $wrappedKey;
+ /**
+ * @var \Yoti\Protobuf\Compubapi\EncryptedData
+ */
+ private $encryptedDataProto;
+
/**
* Setup test data.
*/
diff --git a/tests/ShareUrl/Policy/WantedAttributeBuilderTest.php b/tests/ShareUrl/Policy/WantedAttributeBuilderTest.php
index 5abb542e..2b1c20f3 100644
--- a/tests/ShareUrl/Policy/WantedAttributeBuilderTest.php
+++ b/tests/ShareUrl/Policy/WantedAttributeBuilderTest.php
@@ -32,6 +32,7 @@ public function testBuild()
$wantedAttribute = (new WantedAttributeBuilder())
->withName($someName)
->withDerivation($someDerivation)
+ ->withOptional(false)
->build();
$expectedJsonData = [
@@ -91,6 +92,7 @@ public function testAcceptSelfAsserted()
$wantedAttributeDefault = (new WantedAttributeBuilder())
->withName($someName)
->withAcceptSelfAsserted()
+ ->withOptional(false)
->build();
$this->assertEquals(json_encode($expectedJsonData), json_encode($wantedAttributeDefault));
@@ -124,6 +126,7 @@ public function testWithoutAcceptSelfAsserted()
$wantedAttribute = (new WantedAttributeBuilder())
->withName($someName)
->withAcceptSelfAsserted(false)
+ ->withOptional(false)
->build();
$this->assertEquals(json_encode($expectedJsonData), json_encode($wantedAttribute));
@@ -149,6 +152,7 @@ public function testWithConstraints()
$wantedAttribute = (new WantedAttributeBuilder())
->withName($someName)
+ ->withOptional(false)
->withConstraints($constraints)
->build();
diff --git a/tests/Util/JsonTest.php b/tests/Util/JsonTest.php
index df232c6e..30a9b29b 100644
--- a/tests/Util/JsonTest.php
+++ b/tests/Util/JsonTest.php
@@ -81,18 +81,27 @@ public function testWithoutNullValues()
*/
public function testConvertFromLatin1ToUtf8Recursively()
{
- $latin1String = utf8_decode('éàê');
- $latin1Array = [utf8_decode('éàê'), utf8_decode('çî')];
- $nestedLatin1Array = [utf8_decode('éàê'), [utf8_decode('çî'), utf8_decode('üñ')]];
+ $latin1String = mb_convert_encoding('éàê', 'ISO-8859-1', 'UTF-8');
+ $latin1Array = [
+ mb_convert_encoding('éàê', 'ISO-8859-1', 'UTF-8'),
+ mb_convert_encoding('çî', 'ISO-8859-1', 'UTF-8')
+ ];
+ $nestedLatin1Array = [
+ mb_convert_encoding('éàê', 'ISO-8859-1', 'UTF-8'),
+ [
+ mb_convert_encoding('çî', 'ISO-8859-1', 'UTF-8'),
+ mb_convert_encoding('üñ', 'ISO-8859-1', 'UTF-8')
+ ]
+ ];
$latin1Object = new \stdClass();
- $latin1Object->property1 = utf8_decode('éàê');
- $latin1Object->property2 = utf8_decode('çî');
+ $latin1Object->property1 = mb_convert_encoding('éàê', 'ISO-8859-1', 'UTF-8');
+ $latin1Object->property2 = mb_convert_encoding('çî', 'ISO-8859-1', 'UTF-8');
$nestedLatin1Object = new \stdClass();
- $nestedLatin1Object->property = utf8_decode('çî');
+ $nestedLatin1Object->property = mb_convert_encoding('çî', 'ISO-8859-1', 'UTF-8');
$latin1ObjectWithNestedObject = new \stdClass();
- $latin1ObjectWithNestedObject->property1 = utf8_decode('éàê');
+ $latin1ObjectWithNestedObject->property1 = mb_convert_encoding('éàê', 'ISO-8859-1', 'UTF-8');
$latin1ObjectWithNestedObject->property2 = $nestedLatin1Object;
$this->assertSame('éàê', Json::convertFromLatin1ToUtf8Recursively($latin1String));
diff --git a/tests/YotiClientTest.php b/tests/YotiClientTest.php
index 9f345a5a..d95b635f 100755
--- a/tests/YotiClientTest.php
+++ b/tests/YotiClientTest.php
@@ -7,6 +7,7 @@
use GuzzleHttp\Psr7;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface;
use Yoti\Aml\Address as AmlAddress;
use Yoti\Aml\Country as AmlCountry;
@@ -122,8 +123,12 @@ private function assertApiUrlStartsWith($expectedUrl, $clientApiUrl = null)
*/
public function testGetActivityDetails()
{
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn(file_get_contents(TestData::RECEIPT_JSON));
+ $stream->method('__toString')->willReturn(file_get_contents(TestData::RECEIPT_JSON));
+
$response = $this->createMock(ResponseInterface::class);
- $response->method('getBody')->willReturn(file_get_contents(TestData::RECEIPT_JSON));
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
$httpClient = $this->createMock(ClientInterface::class);
@@ -220,20 +225,21 @@ public function testGetLoginUrl()
*/
public function testCustomLogger()
{
- $response = $this->createMock(ResponseInterface::class);
+ $jsonstr = json_encode([
+ 'receipt' => [
+ 'timestamp' => 'some invalid timestamp',
+ 'wrapped_receipt_key' => 'some receipt key',
+ 'sharing_outcome' => 'SUCCESS',
+ ]
+ ]);
+ $stream = $this->createMock(StreamInterface::class);
+ $stream->method('getContents')->willReturn($jsonstr);
+ $stream->method('__toString')->willReturn($jsonstr);
+
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn($stream);
$response->method('getStatusCode')->willReturn(200);
- $response
- ->method('getBody')
- ->willReturn(
- json_encode([
- 'receipt' => [
- 'timestamp' => 'some invalid timestamp',
- 'wrapped_receipt_key' => 'some receipt key',
- 'sharing_outcome' => 'SUCCESS',
- ]
- ])
- );
$httpClient = $this->createMock(ClientInterface::class);
$httpClient->expects($this->exactly(1))