diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dec1cd..dd5be20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [4.0.3] - 2023-09-27 ### Changed - ID type is now optional for docV ### Added +- Enhanced Document Verification to the SmileIdentityCore class. - Business verification to the SmileIdentityCore class. - JobType, ImageType, and BusinessVerificationType enum classes - [examples](/examples) folder diff --git a/examples/enhanced_document_verification.php b/examples/enhanced_document_verification.php new file mode 100644 index 0000000..414dd31 --- /dev/null +++ b/examples/enhanced_document_verification.php @@ -0,0 +1,86 @@ +'; +$default_callback = ''; +// You can download your API key from the Smile Identity portal. +// NOTE: The sandbox and production servers use different API keys. +$api_key = ''; +// Use '0' for the sandbox (test) server, use '1' for production server +$sid_server = '0'; + +$sid_core = new SmileIdentityCore( + $partner_id, + $default_callback, + $api_key, + $sid_server +); + +// Create required tracking parameters +// Every communication between your server and the Smile Identity servers contain these parameters. +// Use them to match up the job results with the job and user you submitted. +$partner_params = array( + 'job_id' => '', + 'user_id' => ' JobType::ENHANCED_DOCUMENT_VERIFICATION, +); + +// The ID Document Information +$id_info = array( + 'country' => '<2-letter country code>', + 'id_type' => '' +); + +// Create image list +// image_type_id Integer +// 0 - Selfie image jpg or png (if you have the full path of the selfie) +// 2 - Selfie image jpg or png base64 encoded (if you have the base64image string of the selfie) +// 4 - Liveness image jpg or png (if you have the full path of the liveness image) +// 6 - Liveness image jpg or png base64 encoded (if you have the base64image string of the liveness image) +// 1 - Front of ID document image jpg or png (if you have the full path of the selfie) +// 3 - Front of ID document image jpg or png base64 encoded (if you have the base64image string of the selfie) +// 5 - Back of ID document image jpg or png (if you have the full path of the selfie) +// 7 - Back of ID document image jpg or png base64 encoded (if you have the base64image string of the selfie) +$selfie_filename = 'tmp/selife.jpg'; // Path to selfie image file +$id_card_filename = 'tmp/idcard.jpg'; // Path to idcard image file +$selfie_image_detail = array( + 'image_type_id' => ImageType::SELFIE_FILE, // Selfie image jpg or png + 'image' => $selfie_filename +); +// ID card image can be omitted if selfie comparison to issuer image is desired +$id_card_image_detail = array( + 'image_type_id' => ImageType::ID_CARD_FILE, // ID card image jpg or png + 'image' => $id_card_filename +); +$image_details = array( + $selfie_image_detail, + $id_card_image_detail +); + +// Create options +$options = array( + // Per job callback. If blank the default_callback is used + 'optional_callback' => '', + // Set to true if you want to get the job result in sync (in addition to the result been sent + // to your callback). If set to false, result is sent to callback url only. + 'return_job_status' => '', + // Set to true to receive all of the updates you would otherwise have received in your callback + // as opposed to only the final result. You must set return_job_status to true to use this flag. + 'return_history' => '', + // Set to true to receive links to the selfie and the photo + // it was compared to. You must set return_job_status to true to use this flag. + 'return_image_links' => '' +); + +// submit_job returns an array with at least a Boolean using the key "success" and the Smile Identity job number. +// If options.return_job_status is true it will add to the array the returned job_status information. +$result = $sid_core->submit_job($partner_params, $image_details, $id_info, $options); diff --git a/lib/Config.php b/lib/Config.php index eee3156..00e0583 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -5,7 +5,7 @@ class Config { const SDK_CLIENT = 'PHP'; - const VERSION = '4.0.2'; + const VERSION = '4.0.3'; const DEFAULT_JOB_STATUS_TIMEOUT = 20; const DEFAULT_JOB_STATUS_SLEEP = 2; const SID_SERVERS = [ diff --git a/lib/JobType.php b/lib/JobType.php index f1c03f2..8b383c4 100644 --- a/lib/JobType.php +++ b/lib/JobType.php @@ -30,4 +30,7 @@ class JobType // Compares document verification to an id check const COMPARE_USER_INFO = 9; + + // Verifies user selfie with info retrieved from the ID issuing authority. + const ENHANCED_DOCUMENT_VERIFICATION = 11; } diff --git a/lib/SmileIdentityCore.php b/lib/SmileIdentityCore.php index 0ffab3f..bee0256 100644 --- a/lib/SmileIdentityCore.php +++ b/lib/SmileIdentityCore.php @@ -94,16 +94,14 @@ public function submit_job($partner_params, $image_details, $id_info, $_options) return $id_api->submit_job($partner_params, $id_info, $options); } - validateJobTypes( - [ - JobType::BIOMETRIC_KYC, - JobType::DOCUMENT_VERIFICATION, - JobType::SMART_SELFIE_AUTHENTICATION, - JobType::SMART_SELFIE_REGISTRATION, - JobType::BUSINESS_VERIFICATION - ], - $job_type - ); + validateJobTypes(array( + JobType::BIOMETRIC_KYC, + JobType::BUSINESS_VERIFICATION, + JobType::DOCUMENT_VERIFICATION, + JobType::ENHANCED_DOCUMENT_VERIFICATION, + JobType::SMART_SELFIE_AUTHENTICATION, + JobType::SMART_SELFIE_REGISTRATION, + ), $job_type); if ($job_type === JobType::BUSINESS_VERIFICATION) { return $this->submit_kyb_job($partner_params, $id_info); diff --git a/lib/utils.php b/lib/utils.php index 08f8d98..ea2f1a0 100644 --- a/lib/utils.php +++ b/lib/utils.php @@ -46,13 +46,15 @@ function validateIdParams($id_params, $job_type) throw new Exception("Please ensure that you send through partner params"); } - $not_doc_or_biz_verification = !in_array($job_type, array(JobType::DOCUMENT_VERIFICATION, JobType::BUSINESS_VERIFICATION)); + $not_doc_or_biz_verification = !in_array($job_type, array(JobType::DOCUMENT_VERIFICATION, JobType::ENHANCED_DOCUMENT_VERIFICATION, JobType::BUSINESS_VERIFICATION)); if ($not_doc_or_biz_verification && (!key_exists("entered", $id_params) || strtolower("{$id_params['entered']}") !== "true")) { return; } if ($job_type == JobType::DOCUMENT_VERIFICATION) { $required_fields = ["country"]; + } else if ($job_type == JobType::ENHANCED_DOCUMENT_VERIFICATION) { + $required_fields = ["country", "id_type"]; } else { $required_fields = ["country", "id_number", "id_type"]; } @@ -98,10 +100,12 @@ function validateImageParams($image_details, $job_type, $use_enrolled_image) $has_selfie = true; } } - if ($job_type == JobType::DOCUMENT_VERIFICATION && !$has_id_image){ - throw new Exception('You are attempting to complete a job type 6 without providing an id card image'); + + $is_docv_job = $job_type == JobType::DOCUMENT_VERIFICATION || $job_type == JobType::ENHANCED_DOCUMENT_VERIFICATION; + if ($is_docv_job && !$has_id_image){ + throw new Exception("You are attempting to complete a job type $job_type without providing an id card image"); } - if (!($has_selfie || ($job_type == JobType::DOCUMENT_VERIFICATION && $use_enrolled_image))) { + if (!($has_selfie || ($is_docv_job && $use_enrolled_image))) { throw new Exception('You need to send through at least one selfie image'); } } diff --git a/tests/SmileIdentityCoreTest.php b/tests/SmileIdentityCoreTest.php index 05ab893..fd4e2c6 100644 --- a/tests/SmileIdentityCoreTest.php +++ b/tests/SmileIdentityCoreTest.php @@ -211,6 +211,49 @@ public function testSubmitJobShouldRequireIdCardImageForJT6(): void $this->sic->submit_job($partnerParams, $imageDetails, $idParams, $this->options); } + /** + * @throws GuzzleException + */ + public function testSubmitJobShouldRequireIdCardImageForJT11(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage("You are attempting to complete a job type 11 without providing an id card image"); + $resp_body = [ + "upload_url" => "https://upload-url.com", + "ref_id" => "212-0000058873-dwhsn9nsax3onnbf8mc1rifirl44z", + "smile_job_id" => "0000058873", + "camera_config" => null, + "code" => 2202 + ]; + $partnerParams = [ + "user_id" => "user-id", + "job_id" => "job-id", + "job_type" => JobType::ENHANCED_DOCUMENT_VERIFICATION, + ]; + $idParams = [ + "country" => "NG", + "id_type" => "PASSPORT", + "id_number" => "A00000000", + ]; + $imageDetails = [["image_type_id" => ImageType::SELFIE_FILE, "image" => "base6image"]]; + $signatureKey = $this->sic->generate_signature(); + $timestamp = $signatureKey['timestamp']; + $signature = $signatureKey['signature']; + $getStatusResult = '{"timestamp": "' . $timestamp . '", "signature": "' . $signature . '", "job_complete": true, "job_success": false, "code": "2302", "result": {}, "history": [], "image_links": {"selfie_image": "https://selfie-image.com"}}'; + + $mock = new MockHandler([ + new Response(200, [], json_encode($resp_body)), + new Response(200, []), + new Response(200, [], $getStatusResult) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->sic->setClient($client); + + $this->sic->submit_job($partnerParams, $imageDetails, $idParams, $this->options); + } + /** * @throws GuzzleException */ @@ -256,7 +299,91 @@ public function testSubmitJobShouldRequireCountryInIdInfoForJT6(): void /** * @throws GuzzleException */ - public function testSubmitJobShouldRequireIdTypeInIdInfoForJT6(): void + public function testSubmitJobShouldRequireCountryInIdInfoForJT11(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage("Please make sure that country is included in the id_info and has a value"); + $resp_body = [ + "upload_url" => "https://upload-url.com", + "ref_id" => "212-0000058873-dwhsn9nsax3onnbf8mc1rifirl44z", + "smile_job_id" => "0000058873", + "camera_config" => null, + "code" => 2202 + ]; + $partnerParams = [ + "user_id" => "user-id", + "job_id" => "job-id", + "job_type" => JobType::ENHANCED_DOCUMENT_VERIFICATION, + ]; + $idParams = [ + "id_type" => "PASSPORT", + "id_number" => "A00000000", + ]; + $imageDetails = [["image_type_id" => ImageType::SELFIE_FILE, "image" => "base6image"]]; + $signatureKey = $this->sic->generate_signature(); + $timestamp = $signatureKey['timestamp']; + $signature = $signatureKey['signature']; + $getStatusResult = '{"timestamp": "' . $timestamp . '", "signature": "' . $signature . '", "job_complete": true, "job_success": false, "code": "2302", "result": {}, "history": [], "image_links": {"selfie_image": "https://selfie-image.com"}}'; + + $mock = new MockHandler([ + new Response(200, [], json_encode($resp_body)), + new Response(200, []), + new Response(200, [], $getStatusResult) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->sic->setClient($client); + + $this->sic->submit_job($partnerParams, $imageDetails, $idParams, $this->options); + } + + /** + * @throws GuzzleException + */ + public function testSubmitJobShouldRequireIdTypeInIdInfoForJT11(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage("Please make sure that id_type is included in the id_info and has a value"); + $resp_body = [ + "upload_url" => "https://upload-url.com", + "ref_id" => "212-0000058873-dwhsn9nsax3onnbf8mc1rifirl44z", + "smile_job_id" => "0000058873", + "camera_config" => null, + "code" => 2202 + ]; + $partnerParams = [ + "user_id" => "user-id", + "job_id" => "job-id", + "job_type" => JobType::ENHANCED_DOCUMENT_VERIFICATION, + ]; + $idParams = [ + "country" => "NG", + "id_number" => "A00000000", + ]; + $imageDetails = [["image_type_id" => ImageType::SELFIE_FILE, "image" => "base6image"]]; + $signatureKey = $this->sic->generate_signature(); + $timestamp = $signatureKey['timestamp']; + $signature = $signatureKey['signature']; + $getStatusResult = '{"timestamp": "' . $timestamp . '", "signature": "' . $signature . '", "job_complete": true, "job_success": false, "code": "2302", "result": {}, "history": [], "image_links": {"selfie_image": "https://selfie-image.com"}}'; + + $mock = new MockHandler([ + new Response(200, [], json_encode($resp_body)), + new Response(200, []), + new Response(200, [], $getStatusResult) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->sic->setClient($client); + + $this->sic->submit_job($partnerParams, $imageDetails, $idParams, $this->options); + } + + /** + * @throws GuzzleException + */ + public function testSubmitJobSuccessForJT6(): void { $resp_body = [ "upload_url" => "https://upload-url.com", @@ -295,6 +422,48 @@ public function testSubmitJobShouldRequireIdTypeInIdInfoForJT6(): void $this->assertTrue($result["success"]); } + /** + * @throws GuzzleException + */ + public function testSubmitJobSuccessForJT11(): void + { + $resp_body = [ + "upload_url" => "https://upload-url.com", + "ref_id" => "212-0000058873-dwhsn9nsax3onnbf8mc1rifirl44z", + "smile_job_id" => "0000058873", + "camera_config" => null, + "code" => 2202 + ]; + $partnerParams = [ + "user_id" => "user-id", + "job_id" => "job-id", + "job_type" => JobType::ENHANCED_DOCUMENT_VERIFICATION, + ]; + $idParams = [ + "country" => "NG", + "id_type" => "PASSPORT" + ]; + $imageDetails = [["image_type_id" => ImageType::SELFIE_FILE, "image" => "base6image"], ["image_type_id" => ImageType::ID_CARD_FILE, "image" => "base6image"]]; + $signatureKey = $this->sic->generate_signature(); + $timestamp = $signatureKey['timestamp']; + $signature = $signatureKey['signature']; + $getStatusResult = '{"timestamp": "' . $timestamp . '", "signature": "' . $signature . '", "job_complete": true, "job_success": false, "code": "2302", "result": {}, "history": [], "image_links": {"selfie_image": "https://selfie-image.com"}}'; + + $mock = new MockHandler([ + new Response(200, [], json_encode($resp_body)), + new Response(200, []), + new Response(200, [], $getStatusResult) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->sic->setClient($client); + + $result = $this->sic->submit_job($partnerParams, $imageDetails, $idParams, $this->options); + + $this->assertTrue($result["success"]); + } + /** * @throws GuzzleException */ @@ -334,7 +503,7 @@ public function testSubmitJobShouldRequiresAtLeastOneSelfieForJTOtherThanJT6(): $client = new Client(['handler' => $handler]); $this->sic->setClient($client); - $this->sic->submit_job($this->partnerParams, $imageDetails, $this->idParams, $this->options); + $this->sic->submit_job($partnerParams, $imageDetails, $idParams, $this->options); } public function testGenerateSignature(): void @@ -465,10 +634,11 @@ public function testExceptionWhenJobTypeIsInvalid() { $expected_values = implode(", ", array( JobType::BIOMETRIC_KYC, + JobType::BUSINESS_VERIFICATION, JobType::DOCUMENT_VERIFICATION, + JobType::ENHANCED_DOCUMENT_VERIFICATION, JobType::SMART_SELFIE_AUTHENTICATION, JobType::SMART_SELFIE_REGISTRATION, - JobType::BUSINESS_VERIFICATION )); $this->expectException(Exception::class); $this->expectExceptionMessage("job_type must be one of $expected_values");