├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── examples └── ws_class.php └── src ├── Actions ├── Messages.php └── Webhook.php ├── Http └── Request.php ├── OAuth └── Config.php └── Whatsapp.php /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | 4 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 5 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 6 | # composer.lock 7 | composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adrián Villamayor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Whatsapp-Api 2 | 3 | [![Latest Stable Version](http://img.shields.io/packagist/v/adrii/whatsapp-api.svg)](https://packagist.org/packages/adrii/whatsapp-api) 4 | [![Total Downloads](http://img.shields.io/packagist/dt/adrii/whatsapp-api.svg)](https://packagist.org/packages/adrii/whatsapp-api) 5 | [![License](http://img.shields.io/packagist/l/adrii/whatsapp-api.svg)](https://packagist.org/packages/adrii/whatsapp-api) 6 | 7 | WhatsApp-Api is a lightweight library to easily interact with cloud APIs of the [WhatsApp Business Platform](https://developers.facebook.com/docs/whatsapp/business-management-api/get-started). 8 | 9 | 10 | 11 | | INDEX | 12 | |-------| 13 | | [Installation](https://github.com/AdrianVillamayor/Whatsapp-Api#installation) | 14 | | [Configutation](https://github.com/AdrianVillamayor/Whatsapp-Api#configutation) | 15 | | [Webhook](https://github.com/AdrianVillamayor/Whatsapp-Api#webhook) | 16 | | [Messages](https://github.com/AdrianVillamayor/Whatsapp-Api#messages) | 17 | | [License](https://github.com/AdrianVillamayor/Whatsapp-Api#license) | 18 | 19 | 20 | ## Installation 21 | 22 | Use [Composer](https://getcomposer.org/) to install the library. 23 | 24 | ```bash 25 | composer require adrii/whatsapp-api 26 | ``` 27 | 28 | ### Composer 29 | ```php 30 | use Adrii\Whatsapp\Whatsapp; 31 | ``` 32 | 33 | ## Configutation 34 | 35 | ```php 36 | $graph_version = "v15.0"; 37 | $phone_number_id = "{phone_number_id}"; 38 | $access_token = "{access_token}"; 39 | $recipient_id = "{recipient_id}"; 40 | 41 | $ws = new Whatsapp($phone_number_id, $access_token, $graph_version); 42 | ``` 43 |
44 | 45 | ## Webhook 46 | To be alerted when you receive a message or when the status of a message changes, you need to set up a webhook connection point for your app. 47 | 48 | This method handles the entire connection request on its own. 49 | The access_token is used to validate the connection. 50 | 51 | ```php 52 | $ws->webhook()->connect($_GET); 53 | ``` 54 |
55 | 56 | ## Messages 57 | 58 | | Status | Type | 59 | | ------ | ----------------------------------------------------------------------------- | 60 | | ✅ | [Text](https://github.com/AdrianVillamayor/Whatsapp-Api#text) | 61 | | ✅ | [Template](https://github.com/AdrianVillamayor/Whatsapp-Api#template) | 62 | | ✅ | [Location](https://github.com/AdrianVillamayor/Whatsapp-Api#location) | 63 | | ✅ | [Contact](https://github.com/AdrianVillamayor/Whatsapp-Api#contact) | 64 | | ✅ | [Media](https://github.com/AdrianVillamayor/Whatsapp-Api#media) | 65 | | ✅ | [Interactive](https://github.com/AdrianVillamayor/Whatsapp-Api#interactive) | 66 | 67 | ### Text 68 | Send basic text (emojis allowed). 69 | ```php 70 | $ws->send_message()->text("Aloha 🍍", $recipient_id); 71 | ``` 72 | 73 | ### Template 74 | Send message templates defined in the Meta manager. 75 | ```php 76 | $ws->send_message()->template("hello_world", $recipient_id); 77 | ``` 78 | 79 | Send message templates defined in the Meta manager with parameters 80 | ```php 81 | $component_header = array( 82 | "type" => "header", 83 | "parameters" => array( 84 | array( 85 | "type" => "image", 86 | "image" => array( 87 | "link" => "https://avatars.githubusercontent.com/u/29653964?v=4" 88 | ) 89 | ), 90 | ) 91 | ); 92 | 93 | $component_body = array( 94 | "type" => "body", 95 | "parameters" => array( 96 | array( 97 | "type" => "text", 98 | "text" => "Adrii 🍍" 99 | ) 100 | ) 101 | ); 102 | 103 | $component_button = array( 104 | "type" => "button", 105 | "sub_type" => "url", 106 | "index" => 0, 107 | "parameters" => array( 108 | array( 109 | "type" => "text", 110 | "text" => "https://github.com/AdrianVillamayor/Whatsapp-Api" 111 | ) 112 | ) 113 | ); 114 | 115 | $ws->send_message()->addComponent($component_header, $component_body, $component_button); 116 | 117 | $response = $ws->send_message()->template("sample_purchase_feedback", $recipient_id); 118 | ``` 119 | 120 | ### Location 121 | 122 | Sends a location, through a longitude, latitude and an address. 123 | ```php 124 | $ws->send_message()->location("41.403191", "2.174840", "La Sagrada Família", "C/ De Mallorca, 401, 08013 Barcelona", $recipient_id); 125 | ``` 126 | 127 | ### Contact 128 | 129 | Send a contact message 130 | 131 | The name is the only required parameter, the other data are optional. 132 | ```php 133 | $contact = array( 134 | "addresses" => array( 135 | array( 136 | "city" => "city name", 137 | "country" => "country name", 138 | "country_code" => "code", 139 | "state" => "Contact's State", 140 | "street" => "Contact's Street", 141 | "type" => "Contact's Address Type", 142 | "zip" => "Contact's Zip Code" 143 | ) 144 | ), 145 | 146 | "birthday" => "14-02-1997", 147 | "emails" => array( 148 | array( 149 | "email" => "email", 150 | "type" => "HOME" 151 | ), 152 | array( 153 | "email" => "email", 154 | "type" => "WORK" 155 | ) 156 | ), 157 | "name" => array( 158 | "formatted_name" => "formatted name value", 159 | "middle_name" => "last name value", 160 | ), 161 | "phones" => array( 162 | array( 163 | "phone" => "654034823", 164 | "type" => "MAIN" 165 | ), 166 | array( 167 | "phone" => "Phone number", 168 | "type" => "HOME" 169 | ), 170 | array( 171 | "phone" => "Phone number", 172 | "type" => "WORK" 173 | ) 174 | ), 175 | "urls" => array( 176 | array( 177 | "url" => "some url", 178 | "type" => "WORK" 179 | ) 180 | ) 181 | ); 182 | 183 | 184 | $ws->send_message()->addContact($contact); 185 | 186 | $response = $ws->send_message()->contact($recipient_id); 187 | 188 | ``` 189 | 190 | You can concatenate as many contacts as you want 191 | ```php 192 | $ws->send_message()->addContact($contact_0, $contact_1, ...); 193 | ``` 194 | 195 | ### Media 196 | 197 | Send a media, as a link or id 198 | ```php 199 | $ws->send_message()->media("image", "https://avatars.githubusercontent.com/u/29653964?v=4", $recipient_id); 200 | 201 | $ws->send_message()->media("video", "https://file-examples.com/storage/fe4658769b6331540b05587/2017/04/file_example_MP4_480_1_5MG.mp4", $recipient_id); 202 | 203 | $ws->send_message()->media("document", "https://file-examples.com/storage/fe4658769b6331540b05587/2017/10/file-sample_150kB.pdf", $recipient_id); 204 | 205 | $ws->send_message()->media("audio", "https://file-examples.com/storage/fe4658769b6331540b05587/2017/11/file_example_MP3_700KB.mp3", $recipient_id); 206 | 207 | $ws->send_message()->media("sticker", "https://img-03.stickers.cloud/packs/210a9e68-b249-405f-8ea1-9af015ef074a/webp/c5b7bded-e0f0-4f79-86aa-ffd825aba680.webp", $recipient_id); 208 | ``` 209 | 210 | Describes the specified image or video media with caption. 211 | ```php 212 | $ws->send_message()->media("image", "https://avatars.githubusercontent.com/u/29653964?v=4", $recipient_id, "individual", true, "your-image-caption-to-be-sent"); 213 | 214 | $ws->send_message()->media("video", "https://file-examples.com/storage/fe4658769b6331540b05587/2017/04/file_example_MP4_480_1_5MG.mp4", $recipient_id, "individual", true, "your-video-caption-to-be-sent"); 215 | ``` 216 | 217 | Describes the filename for the specific document. 218 | ```php 219 | $ws->send_message()->media("document", "https://file-examples.com/storage/fe4658769b6331540b05587/2017/10/file-sample_150kB.pdf", $recipient_id, "individual", true, null, "example_filename.pdf"); 220 | ``` 221 | 222 | ### Interactive 223 | 224 | Send an interactive message with reply buttons 225 | ```php 226 | $button = [ 227 | "header" => "Header", 228 | "body" => "Body", 229 | "footer" => "Footer", 230 | "action" => [ 231 | "buttons" => [ 232 | [ 233 | "type" => "reply", 234 | "reply" => [ 235 | "id" => "UNIQUE_BUTTON_ID_1", 236 | "title" => "BUTTON_TITLE_1" 237 | ] 238 | ], 239 | [ 240 | "type" => "reply", 241 | "reply" => [ 242 | "id" => "UNIQUE_BUTTON_ID_2", 243 | "title" => "BUTTON_TITLE_2" 244 | ] 245 | ] 246 | ] 247 | ] 248 | ]; 249 | 250 | $ws->send_message()->interactive($button, $recipient_id, "button"); 251 | 252 | ``` 253 | 254 | Send an interactive message with list of buttons 255 | ```php 256 | $list = [ 257 | "header" => "Test Header", 258 | "body" => "Test Body", 259 | "footer" => "Test Footer", 260 | "action" => [ 261 | "button" => "BUTTON_TEXT", 262 | "sections" => [ 263 | [ 264 | "title" => "SECTION_1_TITLE", 265 | "rows" => 266 | [ 267 | [ 268 | "id" => "SECTION_1_ROW_1_ID", 269 | "title" => "SECTION_1_ROW_1_TITLE", 270 | "description" => "SECTION_1_ROW_1_DESCRIPTION" 271 | ], 272 | [ 273 | "id" => "SECTION_1_ROW_2_ID", 274 | "title" => "SECTION_1_ROW_2_TITLE", 275 | "description" => "SECTION_1_ROW_2_DESCRIPTION" 276 | ] 277 | ] 278 | ], 279 | [ 280 | "title" => "SECTION_2_TITLE", 281 | "rows" => [ 282 | [ 283 | "id" => "SECTION_2_ROW_1_ID", 284 | "title" => "SECTION_2_ROW_1_TITLE", 285 | "description" => "SECTION_2_ROW_1_DESCRIPTION" 286 | ], 287 | [ 288 | "id" => "SECTION_2_ROW_2_ID", 289 | "title" => "SECTION_2_ROW_2_TITLE", 290 | "description" => "SECTION_2_ROW_2_DESCRIPTION" 291 | ] 292 | ] 293 | ] 294 | ] 295 | ] 296 | ]; 297 | 298 | $ws->send_message()->interactive($list, $recipient_id, "list"); 299 | 300 | ``` 301 | 302 | > ### [Data examples for each message](https://developers.facebook.com/docs/whatsapp/on-premises/webhooks/inbound#mentions) 303 | 304 |
305 | 306 | # Contributing 307 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 308 | 309 | Please make sure to update tests as appropriate. 310 | 311 | # License 312 | [MIT](https://github.com/AdrianVillamayor/Whatsapp-Api/blob/master/LICENSE) 313 | 314 | ### Thanks for your help! 🎉 315 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adrii/whatsapp-api", 3 | "description": "WhatsApp-Api is a lightweight library to easily interact with cloud APIs of the WhatsApp Business Platform.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Adrian", 9 | "email": "adrian.villamayor@gmail.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Adrii\\Whatsapp\\": "src/" 15 | } 16 | }, 17 | "require": { 18 | "adrii/curl-helper": "^1.8" 19 | }, 20 | 21 | "minimum-stability": "dev" 22 | } 23 | -------------------------------------------------------------------------------- /examples/ws_class.php: -------------------------------------------------------------------------------- 1 | ws = new Whatsapp($this->phone_number_id, $this->access_token, $this->graph_version); 20 | } 21 | 22 | // Método para manejar webhooks 23 | public function webhook() 24 | { 25 | // Obtiene el método HTTP usado en la solicitud actual 26 | $method = $GLOBALS['_SERVER']['REQUEST_METHOD']; 27 | switch ($method) { 28 | case "GET": // Si el método es GET 29 | $this->get(); 30 | break; 31 | case "POST": // Si el método es POST 32 | $this->save(); 33 | break; 34 | case "PUT": // Si el método es PUT 35 | // No realiza ninguna acción en caso de PUT 36 | break; 37 | } 38 | } 39 | 40 | // Método para manejar solicitudes GET 41 | public function get() 42 | { 43 | // Verifica si hay datos en la variable global $_GET 44 | if (!empty($_GET)) { 45 | // Conecta el webhook y pasa los parámetros GET 46 | $this->ws->webhook()->connect($_GET); 47 | } 48 | 49 | // Envía una respuesta HTTP con código 404 si no se cumplen condiciones anteriores 50 | $this->response(array('code' => '404'), 404); 51 | } 52 | 53 | // Método para iniciar un proceso o acción 54 | public function start() 55 | { 56 | // Almacena los datos recibidos por POST 57 | $data = $_POST; 58 | 59 | // Crea un componente de cabecera para enviar en un mensaje 60 | $component_header = array( 61 | "type" => "header", 62 | "parameters" => array( 63 | array( 64 | "type" => "text", 65 | "text" => $data['name'] 66 | ) 67 | ) 68 | ); 69 | 70 | // Envía el componente como parte de un mensaje 71 | $this->ws->send_message()->addComponent($component_header); 72 | // Envía un mensaje con una plantilla específica 73 | $this->ws->send_message()->template("template_id", $data['recipient_id'], "ES"); 74 | 75 | // Envía una respuesta HTTP con código 200 76 | $this->response(array("code" => 200), 200); 77 | } 78 | 79 | // Método para guardar datos recibidos via POST 80 | public function save() 81 | { 82 | // Almacena los datos recibidos por POST 83 | $data = $_POST; 84 | 85 | // Verifica si el array $data está vacío 86 | if (empty($data)) { 87 | // Responde con código 400 si no hay datos 88 | $this->response(array('code' => '400'), 400); 89 | } 90 | 91 | // Verifica si existen ciertas claves en el array $data 92 | if ( 93 | isset($data['entry']) && 94 | isset($data['entry'][0]) && 95 | isset($data['entry'][0]['changes']) && 96 | isset($data['entry'][0]['changes'][0]) && 97 | isset($data['entry'][0]['changes'][0]['value']) && 98 | isset($data['entry'][0]['changes'][0]['value']['messages']) && 99 | isset($data['entry'][0]['changes'][0]['value']['messages'][0]) 100 | ) { 101 | 102 | // Extrae información específica de los cambios recibidos 103 | $msg = $data['entry'][0]['changes'][0]['value']['messages'][0]; 104 | $user_id = $data['entry'][0]['changes'][0]['value']['contacts'][0]['wa_id']; 105 | $msg_type = $msg['type']; 106 | 107 | // Responde con los datos del mensaje y usuario 108 | $this->response(array( 109 | 'code' => '200', 110 | $data => array( 111 | "msg" => $msg, 112 | "user_id" => $user_id, 113 | "msg_type" => $msg_type 114 | ) 115 | ), 200); 116 | } 117 | 118 | // Responde con código 400 si no se cumplen las condiciones para extraer datos 119 | $this->response(array('code' => '400'), 400); 120 | } 121 | 122 | // Método privado para enviar respuestas HTTP 123 | private function response($data, $http_code = 200) 124 | { 125 | // Establece el tipo de contenido como JSON y envía los códigos de estado HTTP 126 | header('Content-Type: application/json'); 127 | header('HTTP/1.1: ' . $http_code); 128 | header('Status: ' . $http_code); 129 | exit(json_encode($data)); // Termina la ejecución y devuelve los datos en formato JSON 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Actions/Messages.php: -------------------------------------------------------------------------------- 1 | config = $config; 22 | $this->http_request = new Request(); 23 | } 24 | 25 | /** 26 | * https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages?locale=en_US#text-object 27 | * @param string $message 28 | * @param string $recipientId 29 | * @param string $recipientType 30 | * @param bool $previewUrl 31 | * @return mixed 32 | */ 33 | public function text(string $message, string $recipientId, string $recipientType = "individual", bool $previewUrl = true) 34 | { 35 | $data = [ 36 | "messaging_product" => "whatsapp", 37 | "recipient_type" => $recipientType, 38 | "to" => $recipientId, 39 | "type" => "text", 40 | "text" => ["preview_url" => $previewUrl, "body" => $message] 41 | ]; 42 | 43 | $url = $this->config->getApiUri($this->uri); 44 | $bearer = $this->config->getAccessToken(); 45 | $headers = ["Authorization" => "Bearer {$bearer}"]; 46 | 47 | $response = $this->http_request->post($url, $data, $headers); 48 | 49 | return $response; 50 | } 51 | 52 | /** 53 | * https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-message-templates#media-based 54 | * @param array ...$components 55 | * @return array 56 | */ 57 | public function addComponent(array ...$components) 58 | { 59 | if (empty($components)) throw new \Exception("Component cannot be empty"); 60 | 61 | foreach ($components as $component) { 62 | $this->components[] = $component; 63 | } 64 | 65 | return $this->components; 66 | } 67 | 68 | /** 69 | * https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages?locale=en_US#template-object 70 | * @param string $template 71 | * @param string $recipientId 72 | * @param string $lang 73 | * @param ?array $components 74 | * @return mixed 75 | */ 76 | public function template(string $template, string $recipientId, string $lang = "en_US") 77 | { 78 | $data = [ 79 | "messaging_product" => "whatsapp", 80 | "to" => $recipientId, 81 | "type" => "template", 82 | "template" => [ 83 | "name" => $template, 84 | "language" => ["code" => $lang] 85 | ], 86 | ]; 87 | 88 | if (!empty($this->components)) { 89 | $data['template']["components"] = $this->components; 90 | $this->components = []; 91 | } 92 | 93 | $url = $this->config->getApiUri($this->uri); 94 | $bearer = $this->config->getAccessToken(); 95 | $headers = ["Authorization" => "Bearer {$bearer}"]; 96 | 97 | $response = $this->http_request->post($url, $data, $headers); 98 | 99 | return $response; 100 | } 101 | 102 | /** 103 | * https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages?locale=en_US#location-object 104 | * @param string $lat 105 | * @param string $long 106 | * @param string $name 107 | * @param string $address 108 | * @param string $recipientId 109 | * @return mixed 110 | */ 111 | public function location(string $lat, string $long, string $name, string $address, string $recipientId) 112 | { 113 | $data = [ 114 | "messaging_product" => "whatsapp", 115 | "to" => $recipientId, 116 | "type" => "location", 117 | "location" => [ 118 | "latitude" => $lat, 119 | "longitude" => $long, 120 | "name" => $name, 121 | "address" => $address, 122 | ], 123 | ]; 124 | 125 | $url = $this->config->getApiUri($this->uri); 126 | $bearer = $this->config->getAccessToken(); 127 | $headers = ["Authorization" => "Bearer {$bearer}"]; 128 | 129 | 130 | $response = $this->http_request->post($url, $data, $headers); 131 | 132 | return $response; 133 | } 134 | 135 | 136 | /** 137 | * https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-message-templates#media-based 138 | * @param array ...$components 139 | * @return array 140 | */ 141 | public function addContact(array ...$contacts): array 142 | { 143 | if (empty($contacts)) throw new \Exception("Contacts cannot be empty"); 144 | 145 | foreach ($contacts as $contact) { 146 | if (empty($contact)) throw new \Exception("Contact cannot be empty"); 147 | 148 | $obj = array(); 149 | 150 | if (!isset($contact['name']) && empty($contact['name'])) { 151 | throw new \Exception("Contact name is required"); 152 | } 153 | 154 | if (!isset($contact['name']['formatted_name']) || (!isset($contact['name']['first_name']) && !isset($contact['name']['middle_name']))) { 155 | throw new \Exception("Contact name, formatted_name and one of the following parameters are required: first_namestring or middle_namestring."); 156 | } 157 | 158 | $obj['name'] = $contact['name']; 159 | 160 | unset($contact['name']); 161 | 162 | if (isset($contact['birthday']) && !empty($contact['birthday'])) { 163 | $timestamp = \strtotime($contact['birthday']); 164 | 165 | if ($timestamp) { 166 | $birthday = date("Y-m-d", \strtotime($contact['birthday'])); 167 | 168 | $obj['birthday'] = $birthday; 169 | 170 | unset($contact['birthday']); 171 | } else { 172 | throw new \Exception("Contact birthday format is not correct, YYYYY-MM-DD formatted string."); 173 | } 174 | } 175 | 176 | $keys = array("addresses", "birthday", "name", "org", "phones", "urls"); 177 | 178 | foreach ($contact as $key => $value) { 179 | if (in_array($key, $keys) && !empty($contact[$key])) { 180 | 181 | if ($key == "addresses" || $key == "phones" || $key == "urls") { 182 | if (!isset($contact[$key][0]) || is_array($contact[$key][0]) === false) { 183 | throw new \Exception("Contact {$key} must be an array."); 184 | } 185 | } 186 | 187 | $obj[$key] = $value; 188 | } 189 | } 190 | 191 | $this->contacts[] = $obj; 192 | } 193 | 194 | return $this->contacts; 195 | } 196 | 197 | 198 | /** 199 | * https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages?locale=en_US#contacts-object 200 | * @param string $recipientId 201 | * @return mixed 202 | */ 203 | 204 | public function contact(string $recipientId) 205 | { 206 | try { 207 | if (empty($this->contacts)) throw new \Exception("Contacts cannot be empty"); 208 | 209 | $data = [ 210 | "messaging_product" => "whatsapp", 211 | "to" => $recipientId, 212 | "type" => "contacts", 213 | "contacts" => $this->contacts 214 | ]; 215 | 216 | $url = $this->config->getApiUri($this->uri); 217 | $bearer = $this->config->getAccessToken(); 218 | $headers = ["Authorization" => "Bearer {$bearer}"]; 219 | 220 | 221 | $response = $this->http_request->post($url, $data, $headers); 222 | 223 | return $response; 224 | } catch (Exception $e) { 225 | echo 'Caught Exception: ', $e->getMessage(), "\n"; 226 | } 227 | } 228 | 229 | /** 230 | * https://developers.facebook.com/docs/whatsapp/on-premises/reference/media 231 | * @param string $type [audio, document, image, sticker, or video] 232 | * @param string $media 233 | * @param string $recipientId 234 | * @param string $recipientType 235 | * @param ?string $caption Describes the specified image or video media. 236 | * @param ?string $filename Describes the filename for the specific document. 237 | * @param bool $link only with HTTP/HTTPS URLs 238 | * @return mixed 239 | */ 240 | public function media(string $type, string $media, string $recipientId, string $recipientType = "individual", bool $link = true, ?string $caption = null, ?string $filename = null) 241 | { 242 | try { 243 | $required_types = ["audio", "document", "image", "sticker", "video"]; 244 | 245 | if (empty($type) || !in_array($type, $required_types)) throw new \Exception("Type {$type} is not supported."); 246 | 247 | if ($link) { 248 | $parse_url = parse_url($media); 249 | if (isset($parse_url['scheme']) === false || ($parse_url['scheme'] !== "https" && $parse_url['scheme'] !== "http")) throw new \Exception("The protocol and URL of the media to be sent. Use only with HTTP/HTTPS URLs."); 250 | } 251 | 252 | $obj_id = ($link) ? "link" : "id"; 253 | 254 | $data = [ 255 | "messaging_product" => "whatsapp", 256 | "recipient_type" => $recipientType, 257 | "to" => $recipientId, 258 | "type" => $type, 259 | $type => [ 260 | $obj_id => $media 261 | ] 262 | ]; 263 | 264 | switch ($type) { 265 | case 'image': 266 | case 'video': 267 | if (!empty($caption)) $data[$type]['caption'] = $caption; 268 | break; 269 | 270 | case 'document': 271 | if (!empty($filename)) $data[$type]['filename'] = $filename; 272 | break; 273 | } 274 | 275 | $url = $this->config->getApiUri($this->uri); 276 | $bearer = $this->config->getAccessToken(); 277 | $headers = ["Authorization" => "Bearer {$bearer}"]; 278 | 279 | $response = $this->http_request->post($url, $data, $headers); 280 | 281 | return $response; 282 | } catch (Exception $e) { 283 | echo 'Caught Exception: ', $e->getMessage(), "\n"; 284 | } 285 | } 286 | 287 | 288 | /** 289 | * @param array $button 290 | * @return array 291 | */ 292 | public function createInteraction(array $button, string $type): array 293 | { 294 | $elem = [ 295 | "type" => $type, 296 | "body" => ["text" => $button["body"]], 297 | "action" => $button["action"] 298 | ]; 299 | 300 | if (isset($button['header'])) { 301 | $elem["header"] = ["type" => "text", "text" => $button["header"]]; 302 | } 303 | 304 | if (isset($button['footer'])) { 305 | $elem["footer"] = ["text" => $button["footer"]]; 306 | } 307 | 308 | return $elem; 309 | } 310 | 311 | /** 312 | * https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages?locale=en_US#interactive-object 313 | * @param $button 314 | * @param $recipientId 315 | * @return mixed 316 | */ 317 | public function interactive(array $button, string $recipientId, string $type = "list") 318 | { 319 | $data = [ 320 | "messaging_product" => "whatsapp", 321 | "to" => $recipientId, 322 | "type" => "interactive", 323 | "interactive" => $this->createInteraction($button, $type), 324 | ]; 325 | 326 | $url = $this->config->getApiUri($this->uri); 327 | $bearer = $this->config->getAccessToken(); 328 | $headers = ["Authorization" => "Bearer {$bearer}"]; 329 | 330 | $response = $this->http_request->post($url, $data, $headers); 331 | 332 | return $response; 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/Actions/Webhook.php: -------------------------------------------------------------------------------- 1 | config = $config; 17 | } 18 | 19 | public function connect($get_params) 20 | { 21 | if (!empty($get_params)) { 22 | $mode = isset($get_params["hub.mode"]) ? $get_params["hub.mode"] : $get_params["hub_mode"]; 23 | $token = isset($get_params["hub.verify_token"]) ? $get_params["hub.verify_token"] : $get_params["hub_verify_token"]; 24 | $challenge = isset($get_params["hub.challenge"]) ? $get_params["hub.challenge"] : $get_params["hub_challenge"]; 25 | 26 | if (!empty($mode) && !empty($token)) { 27 | // Check the mode and token sent are correct 28 | if ($mode === "subscribe" && $token === $this->config->getAccessToken()) { 29 | // Respond with 200 OK and challenge token from the request 30 | $http_code = 200; 31 | $data = intval($challenge); 32 | } else { 33 | // Responds with '403 Forbidden' if verify tokens do not match 34 | $http_code = 403; 35 | } 36 | } 37 | } 38 | 39 | header('Content-Type: application/json'); 40 | header('HTTP/1.1: ' . $http_code); 41 | header('Status: ' . $http_code); 42 | exit(json_encode($data)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Http/Request.php: -------------------------------------------------------------------------------- 1 | setUrl($url); 16 | $curl->setGetParams($get_params); 17 | 18 | $curl->setHeaders($headers); 19 | 20 | $curl->setMime("json"); 21 | 22 | $curl->setUtf8(); 23 | 24 | $curl->execute(); 25 | 26 | $response = $curl->response(); 27 | list($error, $msg) = $curl->parseCode(); 28 | 29 | return array($response, $error, $msg); 30 | } 31 | 32 | public function post(string $url, array $post_params, array $headers = []): ?array 33 | { 34 | $curl = new CurlHelper(); 35 | 36 | $curl->setUrl($url); 37 | 38 | $curl->setPostParams($post_params); 39 | 40 | $curl->setHeaders($headers); 41 | 42 | $curl->setMime("json"); 43 | 44 | $curl->execute(); 45 | 46 | $response = $curl->response(); 47 | list($error, $msg) = $curl->parseCode(); 48 | 49 | return array($response, $error, $msg); 50 | } 51 | 52 | public function delete(string $url, array $post_params, array $headers = []): ?array 53 | { 54 | $curl = new CurlHelper(); 55 | 56 | $curl->setUrl($url); 57 | 58 | $curl->setDeleteParams($post_params); 59 | 60 | $curl->setHeaders($headers); 61 | 62 | $curl->setMime("json"); 63 | 64 | $curl->execute(); 65 | 66 | $response = $curl->response(); 67 | list($error, $msg) = $curl->parseCode(); 68 | 69 | return array($response, $error, $msg); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/OAuth/Config.php: -------------------------------------------------------------------------------- 1 | phone_number_id = $phone_number_id; 18 | $this->access_token = $access_token; 19 | $this->api_version = $api_version; 20 | } 21 | 22 | public function getPhoneNumberId(): string 23 | { 24 | return $this->phone_number_id; 25 | } 26 | 27 | public function getAccessToken(): string 28 | { 29 | return $this->access_token; 30 | } 31 | 32 | public function getApiVersion(): string 33 | { 34 | return $this->api_version; 35 | } 36 | 37 | public function getApiUri(string $url = ""): ?string 38 | { 39 | return self::API_URL . $this->getApiVersion() . "/" . $this->getPhoneNumberId() . $url; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Whatsapp.php: -------------------------------------------------------------------------------- 1 | config = new Config($phone_number_id, $access_token, $api_version); 31 | 32 | $this->messages = new Messages($this->config); 33 | $this->webhook = new Webhook($this->config); 34 | } 35 | 36 | public function send_message() 37 | { 38 | return $this->messages; 39 | } 40 | 41 | public function webhook() 42 | { 43 | return $this->webhook; 44 | } 45 | 46 | 47 | /** 48 | * @param $data 49 | * @return mixed 50 | */ 51 | public function preprocess($data) 52 | { 53 | return $data["entry"][0]["changes"][0]["value"]; 54 | } 55 | 56 | /** 57 | * @param $data 58 | * @return mixed|void 59 | */ 60 | public function getMobile($data) 61 | { 62 | $data = $this->preprocess($data); 63 | if (array_key_exists("contacts", $data)) { 64 | return $data["contacts"][0]["wa_id"]; 65 | } 66 | } 67 | 68 | /** 69 | * @param $data 70 | * @return mixed|void 71 | */ 72 | public function getName($data) 73 | { 74 | $contact = $this->preprocess($data); 75 | if ($contact) { 76 | return $contact["contacts"][0]["profile"]["name"]; 77 | } 78 | } 79 | 80 | /** 81 | * @param $data 82 | * @return mixed|void 83 | */ 84 | public function getMessage($data) 85 | { 86 | $data = $this->preprocess($data); 87 | if (array_key_exists("messages", $data)) { 88 | return $data["messages"][0]["text"]["body"]; 89 | } 90 | } 91 | 92 | /** 93 | * @param $data 94 | * @return mixed|void 95 | */ 96 | public function getMessageId($data) 97 | { 98 | $data = $this->preprocess($data); 99 | if (array_key_exists("messages", $data)) { 100 | return $data["messages"][0]["id"]; 101 | } 102 | } 103 | 104 | /** 105 | * @param $data 106 | * @return mixed|void 107 | */ 108 | public function getMessageTimestamp($data) 109 | { 110 | $data = $this->preprocess($data); 111 | if (array_key_exists("messages", $data)) { 112 | return $data["messages"][0]["timestamp"]; 113 | } 114 | } 115 | 116 | /** 117 | * @param $data 118 | * @return mixed|void 119 | */ 120 | public function getInteractiveResponse($data) 121 | { 122 | $data = $this->preprocess($data); 123 | if (array_key_exists("messages", $data)) { 124 | return $data["messages"][0]["interactive"]["list_reply"]; 125 | } 126 | } 127 | 128 | /** 129 | * @param $data 130 | * @return mixed|void 131 | */ 132 | public function getMessageType($data) 133 | { 134 | $data = $this->preprocess($data); 135 | if (array_key_exists("messages", $data)) { 136 | return $data["messages"][0]["type"]; 137 | } 138 | } 139 | 140 | /** 141 | * @param $data 142 | * @return mixed|void 143 | */ 144 | public function getDelivery($data) 145 | { 146 | $data = $this->preprocess($data); 147 | if (array_key_exists("statuses", $data)) { 148 | return $data["statuses"][0]["status"]; 149 | } 150 | } 151 | 152 | /** 153 | * @param $data 154 | * @return mixed 155 | */ 156 | public function changedField($data) 157 | { 158 | return $data["entry"][0]["changes"][0]["field"]; 159 | } 160 | } 161 | --------------------------------------------------------------------------------