├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── RELEASE ├── Vagrantfile ├── Version.php ├── composer.json ├── examples ├── appverify │ └── 1_get_status_by_external_id.php ├── callback_example │ └── telesign_callback.php ├── messaging │ ├── 1_send_message.php │ └── 2_send_message_with_verification_code.php ├── phoneid │ ├── 1_check_phone_type_to_block_voip.php │ └── 2_cleansing.php ├── score │ └── 1_check_phone_number_risk_level.php └── voice │ ├── 1_send_voice_call.php │ ├── 2_send_voice_call_with_verification_code.php │ └── 3_send_voice_call_french.php ├── php_banner.jpg ├── php_banner_enterprise.jpg ├── src ├── appverify │ └── AppVerifyClient.php ├── config.php ├── messaging │ └── MessagingClient.php ├── phoneid │ └── PhoneIdClient.php ├── rest │ ├── Response.php │ └── RestClient.php ├── score │ └── ScoreClient.php ├── util.php └── voice │ └── VoiceClient.php └── test ├── ClientTest.php ├── ConfigTest.php ├── Example.php ├── UtilTest.php ├── autoverify └── AutoVerifyClientTest.php ├── messaging └── MessagingClientTest.php ├── phoneid └── PhoneIdClientTest.php ├── rest ├── ResponseTest.php └── RestClientTest.php ├── score └── ScoreClientTest.php └── voice └── VoiceClientTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /docs/ 3 | /vendor/ 4 | /.vagrant/ 5 | /coverage.xml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '7.2' 4 | - '7.3' 5 | - '7.4' 6 | - '8.0' 7 | install: 8 | - composer install 9 | script: 10 | - composer test 11 | after_success: 12 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Telesign Corp. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![packagist](https://img.shields.io/packagist/v/telesign/telesign.svg)](https://packagist.org/packages/telesign/telesign) [![license](https://img.shields.io/github/license/TeleSign/php_telesign.svg)](https://github.com/TeleSign/php_telesign/blob/master/LICENSE) 2 | 3 | # Telesign Self-service PHP SDK 4 | 5 | [Telesign](https://telesign.com) connects, protects, and defends the customer experience with intelligence from billions of digital interactions and mobile signals. Through developer-friendly APIs that deliver user verification, digital identity, and omnichannel communications, we help the world's largest brands secure onboarding, maintain account integrity, prevent fraud, and streamline omnichannel engagement. 6 | 7 | ## Requirements 8 | 9 | * **PHP 7.2+**. 10 | * **Composer** *(optional)* - This package manager isn't required to use this SDK, but it is required to use the installation instructions below. 11 | 12 | > **NOTE:** 13 | > 14 | > These instructions are for MacOS. They will need to be adapted if you are installing on Windows. 15 | 16 | ## Installation 17 | 18 | Follow these steps to add this SDK as a dependency to your project. 19 | 20 | 1. *(Optional)* Create a new directory for your PHP project. Skip this step if you already have created a project. If you plan to create multiple PHP projects that use Telesign, we recommend that you group them within a `telesign_integrations` directory. 21 | ``` 22 | cd ~/code/local 23 | mkdir telesign_integrations 24 | cd telesign_integrations 25 | mkdir {your project name} 26 | cd {your project name} 27 | ``` 28 | 29 | 2. In the top-level directory for your project, create a new Composer project. 30 | 31 | ``` 32 | composer init 33 | ``` 34 | 35 | Note that this command may need to be adjusted if your composer.phar file is not accessible in your PATH with the "composer" alias. If that is the case, reference the location of composer.phar on your file system for all Composer commands. 36 | 37 | ``` 38 | php {path to file}/composer.phar init 39 | ``` 40 | 41 | 3. Enter the following selections when prompted: 42 | * **Package name (/) [{default vendor name}/{default package name}]:** `{your preferred vendor name}/{your project name}` Use the same project name you chose for the top-level directory in step 1 above. 43 | * **Description []:** Enter your preferred description or use the default. 44 | * **Author [{default author name and email address}, n to skip]:** Enter your preferred description, use the default, or skip. 45 | * **Minimum Stability []:** Enter your preferred value here or skip. 46 | * **Package Type (e.g. library, project, metapackage, composer-plugin) []:** Enter your preferred package type. 47 | * **License []:** Enter your preferred value here or skip. 48 | * **Would you like to define your dependencies (require) interactively [yes]?** Enter your preferred value here or use the default. 49 | * **Would you like to define your dev dependencies (require-dev) interactively [yes]?** Enter your preferred value here or use the default. 50 | * **Would you like to define your dev dependencies (require-dev) interactively [yes]?** Enter your preferred value here or use the default. 51 | * **Add PSR-4 autoload mapping? Maps namespace "{vendor}\{project}" to the entered relative path. [src/, n to skip]:** Enter your preferred value here or use the default. 52 | 53 | 54 | 3. Install the Telesign Self-service PHP SDK as a dependency in the top-level directory of your Composer project using this command. Once the SDK is installed, you should see a message in the terminal notifying you that you have successfully installed the SDK. 55 | 56 | `composer require telesign/telesign` 57 | 58 | ## Authentication 59 | 60 | If you use a Telesign SDK to make your request, authentication is handled behind-the-scenes for you. All you need to provide is your Customer ID and API Key. The SDKs apply Digest authentication whenever they make a request to a Telesign service where it is supported. When Digest authentication is not supported, the SDKs apply Basic authentication. 61 | 62 | ## What's next 63 | 64 | * Learn to send a request to Telesign with code with one of our [tutorials](https://developer.telesign.com/enterprise/docs/tutorials). 65 | * Browse our [Developer Portal](https://developer.telesign.com) for tutorials, how-to guides, reference content, and more. 66 | * Check out our [sample code](https://github.com/TeleSign/sample_code) on GitHub. 67 | -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- 1 | 3.0.7 2 | - Added support for PATCH requests 3 | 4 | 3.0.6 5 | - Add tracking of when SDK is used as a dependency 6 | 7 | 3.0.5 8 | - Added tracking to requests 9 | 10 | 3.0.4 11 | - Remove overloading of Phone ID 12 | 13 | 3.0.3 14 | - Added support for request bodies with application/json content-type 15 | 16 | 3.0.2 17 | - Added dependency for extension mbstring 18 | - Fixed deprecated function utf8_encode() in RestClient.php, changed it with mb_convert_encoding() 19 | 20 | 3.0.1 21 | - Updated to fix support for PHP 8+ 22 | 23 | 3.0.0 24 | - Drop support for PHP5. 25 | - Update dependencies and code to support PHP 7.2+ 26 | 27 | 2.2.3 28 | - Fixes #15 the default case for PhoneID 29 | 30 | 2.2.2 31 | 32 | - Added support for application/json content-type 33 | 34 | 2.2.1 35 | 36 | - enable http put requests 37 | 38 | 2.2.0 39 | 40 | - re-brand Auto Verify to App Verify and refactor associated names 41 | 42 | 2.1.0 43 | 44 | - updated and improved README 45 | - secret_key refactored to api_key to align with docs and portal 46 | - api_host is now known as rest_endpoint to align with docs and portal 47 | - added travis CI, codecov coverage and additional unit tests 48 | 49 | 2.0.0 50 | 51 | - major refactoring and simplification into generic REST client 52 | - started managing dependencies with Composer 53 | - request headers generation routine is now made static 54 | - added utility functions 55 | - added examples 56 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | remi = ["php72", "php73", "php74", "php80", "php82"] 5 | 6 | composer = <<-SHELL 7 | EXPECTED_SIGNATURE=$(curl https://composer.github.io/installer.sig) 8 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 9 | ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") 10 | 11 | if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] 12 | then 13 | >&2 echo 'ERROR: Invalid installer signature' 14 | rm composer-setup.php 15 | exit 1 16 | fi 17 | 18 | php composer-setup.php --quiet 19 | rm composer-setup.php 20 | mv composer.phar /usr/bin/composer 21 | SHELL 22 | 23 | Vagrant.configure("2") do |config| 24 | config.vm.box = "centos/7" 25 | 26 | config.vm.provision "shell", inline: <<-SHELL 27 | if ! yum info remi-release 28 | then 29 | curl -O http://rpms.remirepo.net/enterprise/remi-release-7.rpm 30 | yum -y install ./remi-release-7.rpm 31 | fi 32 | SHELL 33 | 34 | remi.each do |repo| 35 | config.vm.define "#{repo}" do |i| 36 | i.vm.provision "shell", inline: <<-SHELL 37 | yum-config-manager --enable remi-#{repo} 38 | yum -y install php php-xml php-mbstring php-pecl-zip 39 | if ! composer 40 | then 41 | #{composer} 42 | fi 43 | SHELL 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /Version.php: -------------------------------------------------------------------------------- 1 | getIO(); 12 | 13 | if (!is_dir(__DIR__ . "/.git")) { 14 | $io->writeError("Your local repository is missing."); 15 | return; 16 | } 17 | 18 | $versionParser = new VersionParser(); 19 | 20 | $version = $io->askAndValidate("new version: ", function ($answer) use ($versionParser) { 21 | $versionParser->normalize($answer); 22 | return $answer; 23 | }); 24 | 25 | $confirmed = $io->askConfirmation("Are you sure you want \"$version\" to be the new version of the package? (yes) "); 26 | 27 | if (!$confirmed) { 28 | $io->write("No actions were taken."); 29 | return; 30 | } 31 | 32 | file_put_contents(__DIR__ . "/src/version/version.php", "write($stdout); 49 | 50 | if (!$result) { 51 | $io->writeError($stderr); 52 | return; 53 | } 54 | 55 | $io->write("Done."); 56 | } 57 | 58 | static private function exec ($commands, &$stdout, &$stderr) { 59 | foreach ($commands as $co) { 60 | $process = proc_open($co, [ 61 | 1 => [ "pipe", "w" ], 62 | 2 => [ "pipe", "w" ] 63 | ], $pipes); 64 | 65 | if (!is_resource($process)) { 66 | return false; 67 | } 68 | 69 | $stdout[] = trim(stream_get_contents($pipes[1])); 70 | $stderr = trim(stream_get_contents($pipes[2])); 71 | $returnCode = proc_close($process); 72 | 73 | if ($returnCode !== 0) { 74 | return false; 75 | } 76 | } 77 | 78 | return true; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telesign/telesign", 3 | "description": "TeleSign SDK", 4 | "type": "library", 5 | "keywords": [ 6 | "telesign", "sms", "voice", "mobile", "authentication", "identity", "messaging" 7 | ], 8 | "license": "MIT", 9 | "homepage": "https://github.com/telesign/php_telesign", 10 | "authors": [ 11 | { 12 | "name": "TeleSign Corp.", 13 | "email": "support@telesign.com", 14 | "homepage": "https://www.telesign.com" 15 | } 16 | ], 17 | "require": { 18 | "php": "^7.2.5 || ^8.0", 19 | "ext-mbstring": "*", 20 | "ext-xml": "*", 21 | "guzzlehttp/guzzle": "^7.0", 22 | "ramsey/uuid": "^4", 23 | "psr/http-message": "^1.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^8", 27 | "composer/semver": "^3" 28 | }, 29 | "autoload": { 30 | "psr-4": { "telesign\\sdk\\": "src/" }, 31 | "files": [ "src/util.php" ] 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { "telesign\\sdk\\": "test/"} 35 | }, 36 | "scripts": { 37 | "test": "phpunit --bootstrap ./vendor/autoload.php test/ --coverage-clover coverage.xml --whitelist src/", 38 | "bump-version": "telesign\\sdk\\version\\Version::bumpVersion" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/appverify/1_get_status_by_external_id.php: -------------------------------------------------------------------------------- 1 | status($external_id); 14 | 15 | if ($response->ok) { 16 | printf("App Verify transaction with external_id %s has status code %u and status description %s.", 17 | $external_id, 18 | $response->json['status']['code'], 19 | $response->json['status']['description']); 20 | } 21 | -------------------------------------------------------------------------------- /examples/callback_example/telesign_callback.php: -------------------------------------------------------------------------------- 1 | message($phone_number, $message, $message_type); 16 | -------------------------------------------------------------------------------- /examples/messaging/2_send_message_with_verification_code.php: -------------------------------------------------------------------------------- 1 | message($phone_number, $message, $message_type); 18 | 19 | echo "Please enter the verification code you were sent: "; 20 | 21 | $user_entered_verify_code = trim(fgets(STDIN)); 22 | 23 | if ($verify_code == $user_entered_verify_code) { 24 | echo "Your code is correct."; 25 | } 26 | else { 27 | echo "Your code is incorrect."; 28 | } 29 | -------------------------------------------------------------------------------- /examples/phoneid/1_check_phone_type_to_block_voip.php: -------------------------------------------------------------------------------- 1 | phoneid($phone_number); 15 | 16 | if ($response->ok) { 17 | if ($response->json['phone_type']['code'] == $phone_type_voip) { 18 | echo "Phone number $phone_number is a VOIP phone."; 19 | } 20 | else { 21 | echo "Phone number $phone_number is not a VOIP phone."; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/phoneid/2_cleansing.php: -------------------------------------------------------------------------------- 1 | phoneid($incorrect_phone_number); 16 | 17 | if ($response->ok) { 18 | printf("Cleansed phone number has country code %s and phone number is %s.", 19 | $response->json['numbering']['cleansing']['call']['country_code'], 20 | $response->json['numbering']['cleansing']['call']['phone_number']); 21 | 22 | printf("Original phone number was %s.", 23 | $response->json['numbering']['original']['complete_phone_number']); 24 | } 25 | -------------------------------------------------------------------------------- /examples/score/1_check_phone_number_risk_level.php: -------------------------------------------------------------------------------- 1 | score($phone_number, $account_lifecycle_event); 15 | 16 | if ($response->ok) { 17 | echo "Phone number $phone_number has a '{$response->json["risk"]["level"]}' risk level" 18 | . " and the recommendation is to '{$response->json["risk"]["recommendation"]}' the transaction."; 19 | } 20 | -------------------------------------------------------------------------------- /examples/voice/1_send_voice_call.php: -------------------------------------------------------------------------------- 1 | call($phone_number, $message, $message_type); 16 | -------------------------------------------------------------------------------- /examples/voice/2_send_voice_call_with_verification_code.php: -------------------------------------------------------------------------------- 1 | call($phone_number, $message, $message_type); 19 | 20 | echo "Please enter the verification code you were sent: "; 21 | 22 | $user_entered_verify_code = trim(fgets(STDIN)); 23 | 24 | if ($verify_code == $user_entered_verify_code) { 25 | echo "Your code is correct."; 26 | } 27 | else { 28 | echo "Your code is incorrect."; 29 | } 30 | -------------------------------------------------------------------------------- /examples/voice/3_send_voice_call_french.php: -------------------------------------------------------------------------------- 1 | call($phone_number, $message, $message_type, [ "voice" => "f-FR-fr" ]); 18 | -------------------------------------------------------------------------------- /php_banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeleSign/php_telesign/781df2304c24ade681eececf128aa520f31b19e6/php_banner.jpg -------------------------------------------------------------------------------- /php_banner_enterprise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeleSign/php_telesign/781df2304c24ade681eececf128aa520f31b19e6/php_banner_enterprise.jpg -------------------------------------------------------------------------------- /src/appverify/AppVerifyClient.php: -------------------------------------------------------------------------------- 1 | get(sprintf(self::APPVERIFY_STATUS_RESOURCE, $external_id), $params); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/config.php: -------------------------------------------------------------------------------- 1 | post(self::MESSAGING_RESOURCE, array_merge($other, [ 23 | "phone_number" => $phone_number, 24 | "message" => $message, 25 | "message_type" => $message_type 26 | ])); 27 | } 28 | 29 | /** 30 | * Retrieves the current status of the message. 31 | * 32 | * See https://developer.telesign.com/docs/messaging-api for detailed API documentation. 33 | */ 34 | function status ($reference_id, array $params = []) { 35 | return $this->get(sprintf(self::MESSAGING_STATUS_RESOURCE, $reference_id), $params); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/phoneid/PhoneIdClient.php: -------------------------------------------------------------------------------- 1 | post(sprintf(self::PHONEID_RESOURCE, $phone_number), $fields, null, null, "application/json", "Basic"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/rest/Response.php: -------------------------------------------------------------------------------- 1 | status_code = $response->getStatusCode(); 22 | $this->headers = $response->getHeaders(); 23 | $this->body = $response->getBody()->getContents(); 24 | $this->ok = $this->status_code < 400; 25 | $this->json = json_decode($this->body, true); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/rest/RestClient.php: -------------------------------------------------------------------------------- 1 | customer_id = $customer_id; 51 | $this->api_key = $api_key; 52 | $this->rest_endpoint = $rest_endpoint; 53 | 54 | $this->client = new Client([ 55 | "timeout" => $timeout, 56 | "proxy" => $proxy, 57 | "handler" => $handler 58 | ]); 59 | 60 | $current_version = $sdk_version_origin ?? Config::getVersion(); 61 | $php_version = PHP_VERSION; 62 | $guzzle_version = Client::MAJOR_VERSION; 63 | 64 | $this->user_agent = "TeleSignSDK/php PHP/$php_version Guzzle/$guzzle_version OriginatingSDK/$source SDKVersion/$current_version"; 65 | 66 | if ($source !== 'php_telesign') { 67 | $this->user_agent .= " DependencySDKVersion/$sdk_version_dependency"; 68 | } 69 | } 70 | 71 | function setRestEndpoint($rest_endpoint) { 72 | $this->rest_endpoint = $rest_endpoint; 73 | } 74 | 75 | /** 76 | * Generates the Telesign REST API headers used to authenticate requests. 77 | * 78 | * Creates the canonicalized string_to_sign and generates the HMAC signature. This is used to authenticate requests 79 | * against the Telesign REST API. 80 | * 81 | * See https://developer.telesign.com/docs/authentication-1 for detailed API documentation. 82 | * 83 | * @param string $customer_id Your account customer_id 84 | * @param string $api_key Your account api_key 85 | * @param string $method_name The HTTP method name of the request, should be one of 'POST', 'GET', 'PUT', 'PATCH' or 86 | * 'DELETE' 87 | * @param string $resource The partial resource URI to perform the request against 88 | * @param string $url_encoded_fields HTTP body parameters to perform the HTTP request with, must be urlencoded 89 | * @param string $date The date and time of the request 90 | * @param string $nonce A unique cryptographic nonce for the request 91 | * @param string $user_agent User Agent associated with the request 92 | * @param string $content_type Content-Type to send in header 93 | * @param string $auth_method Authentication method 94 | * 95 | * @return array The Telesign authentication headers 96 | */ 97 | static function generateTelesignHeaders ( 98 | $customer_id, 99 | $api_key, 100 | $method_name, 101 | $resource, 102 | $url_encoded_fields, 103 | $date = null, 104 | $nonce = null, 105 | $user_agent = null, 106 | $content_type = null, 107 | $auth_method = "HMAC-SHA256" 108 | ) { 109 | if (!$date) { 110 | $date = gmdate("D, d M Y H:i:s T"); 111 | } 112 | 113 | if (!$nonce) { 114 | $nonce = Uuid::uuid4()->toString(); 115 | } 116 | 117 | if (!$content_type) { 118 | $content_type = in_array($method_name, ["POST", "PUT"]) ? "application/x-www-form-urlencoded" : ""; 119 | } 120 | 121 | 122 | if ($auth_method === "Basic") { 123 | $credentials = base64_encode("$customer_id:$api_key"); 124 | 125 | $authorization = "Basic $credentials"; 126 | } else { 127 | $string_to_sign_builder = [ 128 | $method_name, 129 | "\n$content_type", 130 | "\n$date", 131 | "\nx-ts-auth-method:$auth_method", 132 | "\nx-ts-nonce:$nonce" 133 | ]; 134 | 135 | if ($content_type && $url_encoded_fields) { 136 | $string_to_sign_builder[] = "\n$url_encoded_fields"; 137 | } 138 | 139 | $string_to_sign_builder[] = "\n$resource"; 140 | 141 | $string_to_sign = join("", $string_to_sign_builder); 142 | 143 | $signature = base64_encode( 144 | hash_hmac("sha256", mb_convert_encoding($string_to_sign, "UTF-8", mb_detect_encoding($string_to_sign)), base64_decode($api_key), true) 145 | ); 146 | $authorization = "TSA $customer_id:$signature"; 147 | } 148 | 149 | $headers = [ 150 | "Authorization" => $authorization, 151 | "Date" => $date, 152 | "Content-Type" => $content_type, 153 | "x-ts-auth-method" => $auth_method, 154 | "x-ts-nonce" => $nonce 155 | ]; 156 | 157 | if ($user_agent) { 158 | $headers["User-Agent"] = $user_agent; 159 | } 160 | 161 | return $headers; 162 | } 163 | 164 | /** 165 | * Generic Telesign REST API POST handler 166 | * 167 | * @param string $resource The partial resource URI to perform the request against 168 | * @param array $fields Body params to perform the POST request with 169 | * @param string $date The date and time of the request 170 | * @param string $nonce A unique cryptographic nonce for the request 171 | * 172 | * @return \telesign\sdk\rest\Response The RestClient Response object 173 | */ 174 | function post (...$args) { 175 | return $this->execute("POST", ...$args); 176 | } 177 | 178 | /** 179 | * Generic Telesign REST API GET handler 180 | * 181 | * @param string $resource The partial resource URI to perform the request against 182 | * @param array $fields Query params to perform the GET request with 183 | * @param string $date The date and time of the request 184 | * @param string $nonce A unique cryptographic nonce for the request 185 | * 186 | * @return \telesign\sdk\rest\Response The RestClient Response object 187 | */ 188 | function get (...$args) { 189 | return $this->execute("GET", ...$args); 190 | } 191 | 192 | /** 193 | * Generic Telesign REST API PUT handler 194 | * 195 | * @param string $resource The partial resource URI to perform the request against 196 | * @param array $fields Query params to perform the DELETE request with 197 | * @param string $date The date and time of the request 198 | * @param string $nonce A unique cryptographic nonce for the request 199 | * 200 | * @return \telesign\sdk\rest\Response The RestClient Response object 201 | */ 202 | function put (...$args) { 203 | return $this->execute("PUT", ...$args); 204 | } 205 | 206 | /** 207 | * Generic Telesign REST API DELETE handler 208 | * 209 | * @param string $resource The partial resource URI to perform the request against 210 | * @param array $fields Query params to perform the DELETE request with 211 | * @param string $date The date and time of the request 212 | * @param string $nonce A unique cryptographic nonce for the request 213 | * 214 | * @return \telesign\sdk\rest\Response The RestClient Response object 215 | */ 216 | function delete (...$args) { 217 | return $this->execute("DELETE", ...$args); 218 | } 219 | 220 | /** 221 | * Generic Telesign REST API PATCH handler 222 | * 223 | * @param string $resource The partial resource URI to perform the request against 224 | * @param array $fields Query params to perform the PATCH request with 225 | * @param string $date The date and time of the request 226 | * @param string $nonce A unique cryptographic nonce for the request 227 | * 228 | * @return \telesign\sdk\rest\Response The RestClient Response object 229 | */ 230 | function patch (...$args) { 231 | return $this->execute("PATCH", ...$args); 232 | } 233 | 234 | /** 235 | * Generic Telesign REST API request handler 236 | * 237 | * @param string $resource The partial resource URI to perform the request against 238 | * @param array $fields Body of query params to perform the HTTP request with 239 | * @param string $date The date and time of the request 240 | * @param string $nonce A unique cryptographic nonce for the request 241 | * @param string $content_type Content-Type to send in header 242 | * @param string $auth_method Authentication method 243 | * 244 | * @return \telesign\sdk\rest\Response The RestClient Response object 245 | */ 246 | protected function execute ($method_name, $resource, $fields = [], $date = null, $nonce = null, $content_type = null, $auth_method = "HMAC-SHA256") { 247 | $content_is_json = $content_type === "application/json"; 248 | 249 | if ($content_is_json) { 250 | $form_body = json_encode($fields, count($fields) === 0 ? JSON_FORCE_OBJECT : 0); 251 | } else { 252 | $url_encoded_fields = http_build_query($fields, "", "&"); 253 | } 254 | 255 | $headers = $this->generateTelesignHeaders( 256 | $this->customer_id, 257 | $this->api_key, 258 | $method_name, 259 | $resource, 260 | $content_is_json ? null : $url_encoded_fields, 261 | $date, 262 | $nonce, 263 | $this->user_agent, 264 | $content_type, 265 | $auth_method 266 | ); 267 | 268 | $option = in_array($method_name, [ "POST", "PUT", "PATCH" ]) ? "body" : "query"; 269 | 270 | return new Response($this->client->request($method_name, $resource, [ 271 | "headers" => $headers, 272 | $option => $content_is_json ? $form_body : $url_encoded_fields, 273 | "http_errors" => false, 274 | "base_uri" => $this->rest_endpoint, 275 | ])); 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/score/ScoreClient.php: -------------------------------------------------------------------------------- 1 | post(sprintf(self::SCORE_RESOURCE, $phone_number), array_merge($other, [ 22 | "account_lifecycle_event" => $account_lifecycle_event 23 | ])); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/util.php: -------------------------------------------------------------------------------- 1 | format(DATE_ISO8601); 10 | } 11 | 12 | /** 13 | * Helper function to generate a random number n digits in length using a system random 14 | */ 15 | function randomWithNDigits ($n) { 16 | $randomDigits = array($n); 17 | 18 | for ($i = 0; $i < $n; ++$i) { 19 | $randomDigits[$i] = rand(1, 9); 20 | } 21 | 22 | return join("", $randomDigits); 23 | } 24 | 25 | /** 26 | * Verify that a callback was made by TeleSign and was not sent by a malicious client by verifying the signature. 27 | * 28 | * @param string $api_key The TeleSign API api_key associated with your account 29 | * @param string $signature The TeleSign Authorization header value supplied in the callback 30 | * @param string $json_str The POST body text, that is, the JSON string sent by TeleSign describing the transaction status 31 | */ 32 | function verifyTelesignCallbackSignature ($api_key, $signature, $json_str) { 33 | $your_signature = base64_encode(hash_hmac("sha256", $json_str, base64_decode($api_key), true)); 34 | 35 | if (strlen($signature) != strlen($your_signature)) { 36 | return false; 37 | } 38 | 39 | // Avoid timing attack with constant time equality check 40 | 41 | return hash_equals($signature, $your_signature); 42 | } 43 | -------------------------------------------------------------------------------- /src/voice/VoiceClient.php: -------------------------------------------------------------------------------- 1 | post(self::VOICE_RESOURCE, array_merge($other, [ 23 | "phone_number" => $phone_number, 24 | "message" => $message, 25 | "message_type" => $message_type 26 | ])); 27 | } 28 | 29 | /** 30 | * Retrieves the current status of the voice call. 31 | * 32 | * See https://developer.telesign.com/docs/voice-api for detailed API documentation. 33 | */ 34 | function status ($reference_id, array $params = []) { 35 | return $this->get(sprintf(self::VOICE_STATUS_RESOURCE, $reference_id), $params); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /test/ClientTest.php: -------------------------------------------------------------------------------- 1 | $method(...$args); 35 | $request = $mock->getLastRequest(); 36 | 37 | $this->assertInstanceOf(RequestInterface::class, $request); 38 | $this->assertEquals($expected_url, $request->getUri()); 39 | 40 | if ($request->getHeaderLine('Content-Type') == "application/json") { 41 | $actual_fields = $request->getBody()->getContents(); 42 | } else { 43 | parse_str($request->getBody()->getContents(), $actual_fields ); 44 | } 45 | 46 | $this->assertEquals($expected_fields, $actual_fields); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /test/ConfigTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($currentVersion, 'dev-master'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/Example.php: -------------------------------------------------------------------------------- 1 | [ 19 | [ "method" => "sms" ] 20 | ], 21 | "recipient" => [ 22 | "phone_number" => self::PHONE_NUMBER 23 | ] 24 | ]; 25 | 26 | return $obj; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/UtilTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($date_iso8601, toUtcRfc3339($date_rfc3339)); 15 | } 16 | 17 | function testRandomWithNDigits () { 18 | $n = 7; 19 | $a = randomWithNDigits($n); 20 | $b = randomWithNDigits($n); 21 | 22 | $this->assertEquals($n, strlen($a)); 23 | $this->assertTrue(ctype_digit($a)); 24 | $this->assertNotEquals($a, $b); 25 | } 26 | 27 | function testVerifyTelesignCallbackSignatureCorrect () { 28 | $api_key = Example::API_KEY; 29 | $header_value = "B97g3N9lPdVaptvifxRau7bzVAC5hhRBZ6HKXABN744="; 30 | $json_str = "{'test': 123}"; 31 | 32 | $this->assertTrue(verifyTelesignCallbackSignature($api_key, $header_value, $json_str)); 33 | } 34 | 35 | function testVerifyTelesignCallbackSignatureIncorrect () { 36 | $api_key = Example::API_KEY; 37 | $header_value = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="; 38 | $json_str = "{'test': 123}"; 39 | 40 | $this->assertFalse(verifyTelesignCallbackSignature($api_key, $header_value, $json_str)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /test/autoverify/AutoVerifyClientTest.php: -------------------------------------------------------------------------------- 1 | "123" ] 20 | ], 21 | self::EXAMPLE_REST_ENDPOINT . "/v1/mobile/verification/status/" . self::EXAMPLE_REFERENCE_ID . "?optional_param=123", 22 | [] 23 | ] 24 | ]; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /test/messaging/MessagingClientTest.php: -------------------------------------------------------------------------------- 1 | "123" ] 23 | ], 24 | self::EXAMPLE_REST_ENDPOINT . "/v1/messaging", 25 | [ 26 | "phone_number" => self::EXAMPLE_PHONE_NUMBER, 27 | "message" => "Your OTP is 12345", 28 | "message_type" => "OTP", 29 | "optional_param" => "123" 30 | ] 31 | ], 32 | [ 33 | MessagingClient::class, 34 | "status", 35 | [ 36 | self::EXAMPLE_REFERENCE_ID, 37 | [ "optional_param" => "123" ] 38 | ], 39 | self::EXAMPLE_REST_ENDPOINT . "/v1/messaging/" . self::EXAMPLE_REFERENCE_ID . "?optional_param=123", 40 | [] 41 | ] 42 | ]; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /test/phoneid/PhoneIdClientTest.php: -------------------------------------------------------------------------------- 1 | "123" ] 21 | ], 22 | "uri" => self::EXAMPLE_REST_ENDPOINT . "/v1/phoneid/" . self::EXAMPLE_PHONE_NUMBER, 23 | '{"optional_param":"123"}' 24 | ], 25 | [ 26 | PhoneIdClient::class, 27 | "phoneid", 28 | [ 29 | self::EXAMPLE_PHONE_NUMBER, 30 | ], 31 | "uri" => self::EXAMPLE_REST_ENDPOINT . "/v1/phoneid/" . self::EXAMPLE_PHONE_NUMBER, 32 | "{}", 33 | ] 34 | ]; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /test/rest/ResponseTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('{', $response->body); 14 | $this->assertNull($response->json); 15 | } 16 | 17 | function testValidJsonResolvesArray () { 18 | $response = new Response(new Psr7Response(200, [], "{}")); 19 | $this->assertEquals("{}", $response->body); 20 | $this->assertIsArray($response->json); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /test/rest/RestClientTest.php: -------------------------------------------------------------------------------- 1 | "param" ]; 21 | const EXAMPLE_URL_ENCODED_FIELDS = "test=param"; 22 | const EXAMPLE_DATE = "Mon, 27 Jan 2017 23:59:59 GMT"; 23 | const EXAMPLE_NONCE = "5ffb243e-8e2a-4a7b-bf22-2b3925b3d6ef"; 24 | 25 | /** 26 | * @dataProvider getRequestExamples 27 | */ 28 | function testTelesignHeadersMatchExample ($data) { 29 | $auth_method = $data["auth_method"] ?? "HMAC-SHA256"; 30 | $actual_headers = RestClient::generateTelesignHeaders( 31 | self::EXAMPLE_CUSTOMER_ID, 32 | self::EXAMPLE_API_KEY, 33 | $data["method_name"], 34 | self::EXAMPLE_RESOURCE, 35 | self::EXAMPLE_URL_ENCODED_FIELDS, 36 | self::EXAMPLE_DATE, 37 | self::EXAMPLE_NONCE, 38 | null, 39 | $data["request"]["headers"]["content-type"], 40 | $auth_method 41 | ); 42 | 43 | $expected_headers = [ 44 | "Authorization" => $data["request"]["headers"]["authorization"], 45 | "Date" => self::EXAMPLE_DATE, 46 | "Content-Type" => $data["request"]["headers"]["content-type"], 47 | "x-ts-auth-method" => $auth_method, 48 | "x-ts-nonce" => self::EXAMPLE_NONCE 49 | ]; 50 | 51 | $this->assertEquals($expected_headers, $actual_headers); 52 | } 53 | 54 | function getRequestExamples () { 55 | return [ 56 | [[ 57 | "method_name" => "POST", 58 | "request" => [ 59 | "uri" => self::EXAMPLE_REST_ENDPOINT . self::EXAMPLE_RESOURCE, 60 | "body" => self::EXAMPLE_URL_ENCODED_FIELDS, 61 | "headers" => [ 62 | "authorization" => "TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:dzREwjKg3/o02ABsf8itcNiNzzKWM293qOavWhfkkok=", 63 | "content-type" => "application/x-www-form-urlencoded", 64 | ] 65 | ] 66 | ]], 67 | [[ 68 | "method_name" => "GET", 69 | "request" => [ 70 | "uri" => self::EXAMPLE_REST_ENDPOINT . self::EXAMPLE_RESOURCE . "?" . self::EXAMPLE_URL_ENCODED_FIELDS, 71 | "body" => "", 72 | "headers" => [ 73 | "authorization" => "TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:rtUnnJ8wPWEq/pxxT5H+Pj78WHicDzkVYP+dIStuKiQ=", 74 | "content-type" => "" 75 | ] 76 | ] 77 | ]], 78 | [[ 79 | "method_name" => "PUT", 80 | "request" => [ 81 | "uri" => self::EXAMPLE_REST_ENDPOINT . self::EXAMPLE_RESOURCE, 82 | "body" => self::EXAMPLE_URL_ENCODED_FIELDS, 83 | "headers" => [ 84 | "authorization" => "TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:U2s4H/VqFVnb/flIk9ynhUn6no+VBi2Jlc4YUh4H08k=", 85 | "content-type" => "application/x-www-form-urlencoded" 86 | ] 87 | ] 88 | ]], 89 | [[ 90 | "method_name" => "DELETE", 91 | "request" => [ 92 | "uri" => self::EXAMPLE_REST_ENDPOINT . self::EXAMPLE_RESOURCE . "?" . self::EXAMPLE_URL_ENCODED_FIELDS, 93 | "body" => "", 94 | "headers" => [ 95 | "authorization" => "TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:PjtMTR0t1JzTZEuB7GKlOpdxpsyaX4Zy+4MBWnwii4w=", 96 | "content-type" => "" 97 | ] 98 | ] 99 | ]], 100 | [[ 101 | "method_name" => "PATCH", 102 | "auth_method" => "Basic", 103 | "request" => [ 104 | "uri" => self::EXAMPLE_REST_ENDPOINT . self::EXAMPLE_RESOURCE, 105 | "body" => self::EXAMPLE_FIELDS, 106 | "headers" => [ 107 | "authorization" => Example::AUTH_BASIC_STRING, 108 | "content-type" => "application/json" 109 | ] 110 | ] 111 | ]], 112 | ]; 113 | } 114 | 115 | /** 116 | * @dataProvider getTelesignHeadersAffectedByOptionalArguments 117 | */ 118 | function testGeneratesTelesignHeadersAffectedByOptionalArguments ($header_name) { 119 | $headers = RestClient::generateTelesignHeaders( 120 | self::EXAMPLE_CUSTOMER_ID, 121 | self::EXAMPLE_API_KEY, 122 | "POST", 123 | self::EXAMPLE_RESOURCE, 124 | self::EXAMPLE_URL_ENCODED_FIELDS 125 | ); 126 | 127 | $this->assertArrayHasKey($header_name, $headers); 128 | $this->assertNotEmpty($headers[$header_name]); 129 | } 130 | 131 | function getTelesignHeadersAffectedByOptionalArguments () { 132 | return [ [ "Date" ], [ "x-ts-nonce" ] ]; 133 | } 134 | 135 | function testUserAgentMatchesFormat () { 136 | $mock = new MockHandler([ new Response() ]); 137 | 138 | $client = new RestClient( 139 | self::EXAMPLE_CUSTOMER_ID, self::EXAMPLE_API_KEY, self::EXAMPLE_REST_ENDPOINT, "php_telesign", null, null, 10, null, $mock 140 | ); 141 | $client->get(self::EXAMPLE_RESOURCE); 142 | 143 | $user_agent = $mock->getLastRequest()->getHeader("user-agent")[0]; 144 | $php_version = PHP_VERSION; 145 | $guzzle_version = Client::MAJOR_VERSION; 146 | $pattern = "`^TeleSignSDK/php PHP/$php_version Guzzle/$guzzle_version OriginatingSDK/php_telesign SDKVersion/[a-zA-Z0-9.\-_]+$`"; 147 | 148 | $this->assertRegExp($pattern, $user_agent); 149 | } 150 | 151 | /** 152 | * @dataProvider getRequestExamples 153 | */ 154 | function testSendsRequest ($data) { 155 | $mock = new MockHandler([ new Response() ]); 156 | 157 | $client = new RestClient( 158 | self::EXAMPLE_CUSTOMER_ID, self::EXAMPLE_API_KEY, self::EXAMPLE_REST_ENDPOINT, "php_telesign", null, null, 10, null, $mock 159 | ); 160 | $auth_method = $data["auth_method"] ?? "HMAC-SHA256"; 161 | $content_type = $data["request"]["headers"]["content-type"]; 162 | $client->{$data["method_name"]}( 163 | self::EXAMPLE_RESOURCE, self::EXAMPLE_FIELDS, self::EXAMPLE_DATE, self::EXAMPLE_NONCE, $content_type, $auth_method 164 | ); 165 | 166 | $request = $mock->getLastRequest(); 167 | 168 | $this->assertInstanceOf(RequestInterface::class, $request); 169 | $this->assertEquals($data["request"]["uri"], $request->getUri()); 170 | $this->assertTrue($request->hasHeader("authorization")); 171 | $this->assertEquals($data["request"]["headers"]["authorization"], $request->getHeader("authorization")[0]); 172 | 173 | $bodyContent = $request->getBody(); 174 | if ($content_type === "application/json") { 175 | $parsedBody = json_decode($bodyContent, true); 176 | $this->assertEquals($data["request"]["body"], $parsedBody); 177 | } else { 178 | $this->assertEquals($data["request"]["body"], $bodyContent); 179 | } 180 | } 181 | 182 | /** 183 | * @dataProvider getRequestExamples 184 | */ 185 | function testReturnsResponse ($data) { 186 | $mock = new MockHandler([ new Response() ]); 187 | 188 | $client = new RestClient( 189 | self::EXAMPLE_CUSTOMER_ID, self::EXAMPLE_API_KEY, self::EXAMPLE_REST_ENDPOINT, "php_telesign", null, null, 10, null, $mock 190 | ); 191 | $response = $client->{$data["method_name"]}( 192 | self::EXAMPLE_RESOURCE, self::EXAMPLE_FIELDS, self::EXAMPLE_DATE, self::EXAMPLE_NONCE 193 | ); 194 | 195 | $this->assertInstanceOf(TelesignSdkRestResponse::class, $response); 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /test/score/ScoreClientTest.php: -------------------------------------------------------------------------------- 1 | "123" ] 22 | ], 23 | self::EXAMPLE_REST_ENDPOINT . "/v1/score/" . self::EXAMPLE_PHONE_NUMBER, 24 | [ 25 | "account_lifecycle_event" => self::EXAMPLE_ACCOUNT_LIFECYCLE_EVENT, 26 | "optional_param" => "123" 27 | ] 28 | ] 29 | ]; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /test/voice/VoiceClientTest.php: -------------------------------------------------------------------------------- 1 | "123" ] 23 | ], 24 | "uri" => self::EXAMPLE_REST_ENDPOINT . "/v1/voice", 25 | [ 26 | "phone_number" => self::EXAMPLE_PHONE_NUMBER, 27 | "message" => "Your OTP is 12345", 28 | "message_type" => "OTP", 29 | "optional_param" => "123" 30 | ] 31 | ], 32 | [ 33 | VoiceClient::class, 34 | "status", 35 | [ 36 | self::EXAMPLE_REFERENCE_ID, 37 | [ "optional_param" => "123" ] 38 | ], 39 | self::EXAMPLE_REST_ENDPOINT . "/v1/voice/" . self::EXAMPLE_REFERENCE_ID . "?optional_param=123", 40 | [] 41 | ] 42 | ]; 43 | } 44 | 45 | } 46 | --------------------------------------------------------------------------------