├── LICENSE ├── README.md ├── banner.webp ├── composer.json └── src ├── Client.php ├── Http ├── ClientHandler.php ├── GuzzleClientHandler.php └── RawResponse.php ├── Message ├── AudioMessage.php ├── Contact │ ├── ContactName.php │ ├── Phone.php │ ├── PhoneType.php │ └── Phones.php ├── ContactMessage.php ├── DocumentMessage.php ├── Error │ └── InvalidMessage.php ├── ImageMessage.php ├── LocationMessage.php ├── Media │ ├── LinkID.php │ ├── MediaID.php │ └── MediaObjectID.php ├── Message.php ├── StickerMessage.php ├── Template │ └── Component.php ├── TemplateMessage.php ├── TextMessage.php └── VideoMessage.php ├── Providers └── WhatsappServiceProvider.php ├── Request.php ├── Request ├── RequestAudioMessage.php ├── RequestContactMessage.php ├── RequestDocumentMessage.php ├── RequestImageMessage.php ├── RequestLocationMessage.php ├── RequestStickerMessage.php ├── RequestTemplateMessage.php ├── RequestTextMessage.php └── RequestVideoMessage.php ├── Response.php ├── Response └── ResponseException.php ├── WhatsAppCloudApi.php └── WhatsAppCloudApiApp.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Asim abdalla] 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 | ![](https://raw.githubusercontent.com/asim-altayb/whatsapp-api-laravel/main/banner.webp) 2 | 3 | 4 | ## THIS LIBRARY CLONED FROM ["netflie/whatsapp-cloud-api"](https://github.com/netflie/whatsapp-cloud-api) AS NATIVE PHP AND DEVELOPED TO SUPPORT LARAVEL APPLICATIONS 5 | 6 | 7 | ## What It Does 8 | This package makes it easy for developers to access [WhatsApp Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api "WhatsApp Cloud API") service in their PHP code. 9 | 10 | The first **1,000 conversations** each month are free from WhatsApp Cloud API. A conversation. 11 | 12 | ## Getting Started 13 | Please create and configure your Facebook WhatsApp application following the ["Get Stared"](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started) section of the official guide. 14 | 15 | Minimum requirements – To run the SDK, your system will require **PHP >= 7.4** with a recent version of **CURL >=7.19.4** compiled with OpenSSL and zlib. 16 | 17 | ## Installation 18 | ```composer require asim-altayb/whatsapp-api-laravel ``` 19 | ## Composer Dump to load files 20 | ```composer dump-autoload ``` 21 | 22 | ## Quick Examples 23 | 24 | ### Send a text message 25 | ```php 26 | 'your-configured-from-phone-number-id', 34 | 'access_token' => 'your-facebook-whatsapp-application-token', 35 | ]); 36 | 37 | $whatsapp_cloud_api->sendTextMessage('96651234567', 'Hey there! I\'m using WhatsApp Cloud API. Visit https://www.AsimAltayb.es'); 38 | ``` 39 | 40 | ### Send a document 41 | You can send documents in two ways: by uploading a file to the WhatsApp Cloud servers (where you will receive an identifier) or from a link to a document published on internet. 42 | 43 | ```php 44 | sendDocument('96651234567', $media_id, $document_name, $document_caption); 56 | 57 | // Or 58 | $document_link = 'https://link.com/image.png'; 59 | $link_id = new LinkID($document_link); 60 | $whatsapp_cloud_api->sendDocument('96651234567', $link_id, $document_name, $document_caption); 61 | ``` 62 | 63 | ### Send a template message 64 | ```php 65 | sendTemplate('96651234567', 'hello_world', 'en_US'); // Language is optional 68 | ``` 69 | 70 | You also can build templates with parameters: 71 | 72 | ```php 73 | 'text', 82 | 'text' => '*Mr Jones*', 83 | ], 84 | ]; 85 | 86 | $component_buttons = [ 87 | [ 88 | 'type' => 'button', 89 | 'sub_type' => 'quick_reply', 90 | 'index' => 0, 91 | 'parameters' => [ 92 | [ 93 | 'type' => 'text', 94 | 'text' => 'Yes', 95 | ] 96 | ] 97 | ], 98 | [ 99 | 'type' => 'button', 100 | 'sub_type' => 'quick_reply', 101 | 'index' => 1, 102 | 'parameters' => [ 103 | [ 104 | 'type' => 'text', 105 | 'text' => 'No', 106 | ] 107 | ] 108 | ] 109 | ]; 110 | 111 | $components = new Component($component_header, $component_body, $component_buttons); 112 | $whatsapp_cloud_api->sendTemplate('96651234567', 'sample_issue_resolution', 'en_US', $components); // Language is optional 113 | ``` 114 | 115 | ### Send an audio message 116 | ```php 117 | sendAudio('96651234567', $link_id); 124 | ``` 125 | 126 | ### Send an image message 127 | ```php 128 | sendImage('', $link_id); 135 | 136 | //or 137 | 138 | $media_id = new MediaObjectID(''); 139 | $whatsapp_cloud_api->sendImage('', $media_id); 140 | ``` 141 | 142 | ### Send a video message 143 | ```php 144 | sendVideo('', $link_id, ''); 151 | 152 | //or 153 | 154 | $media_id = new MediaObjectID(''); 155 | $whatsapp_cloud_api->sendVideo('', $media_id, ''); 156 | ``` 157 | 158 | ### Send a sticker message 159 | 160 | Stickers sample: https://github.com/WhatsApp/stickers 161 | 162 | ```php 163 | sendSticker('', $link_id); 170 | 171 | //or 172 | 173 | $media_id = new MediaObjectID(''); 174 | $whatsapp_cloud_api->sendSticker('', $media_id); 175 | ``` 176 | 177 | ### Send a location message 178 | 179 | ```php 180 | sendLocation('', $longitude, $latitude, $name, $address); 183 | ``` 184 | 185 | ### Send a contact message 186 | 187 | ```php 188 | sendContact('', $name, $phone); 198 | ``` 199 | 200 | ### Send a list message 201 | 202 | ```php 203 | sendList( 220 | '', 221 | 'Rate your experience', 222 | 'Please consider rating your shopping experience in our website', 223 | 'Thanks for your time', 224 | $action 225 | ); 226 | ``` 227 | 228 | ## Features 229 | 230 | - Send Text Messages 231 | - Send Documents 232 | - Send Templates with parameters 233 | - Send Audios 234 | - Send Images 235 | - Send Videos 236 | - Send Stickers 237 | - Send Locations 238 | - Send Contacts 239 | - Send Lists 240 | 241 | ## Getting Help 242 | - Ask a question on the [Discussions forum](https://github.com/asim-altayb/whatsapp-cloud-api/discussions "Discussions forum") 243 | - To report bugs, please [open an issue](https://github.com/asim-altayb/whatsapp-cloud-api/issues/new/choose "open an issue") 244 | 245 | ## Changelog 246 | 247 | Please see [CHANGELOG](https://github.com/netflie/whatsapp-cloud-api/blob/main/CHANGELOG.md "CHANGELOG") for more information what has changed recently. 248 | 249 | ## Testing 250 | ```php 251 | composer unit-test 252 | ``` 253 | You also can run tests making real calls to the WhastApp Clou API. Please put your testing credentials on **WhatsAppCloudApiTestConfiguration** file. 254 | ```php 255 | composer integration-test 256 | ``` 257 | ## Contributing 258 | 259 | Please see [CONTRIBUTING](https://github.com/netflie/.github/blob/master/CONTRIBUTING.md "CONTRIBUTING") for details. 260 | 261 | ## License 262 | 263 | The MIT License (MIT). Please see License File for more information. Please see [License file](https://github.com/netflie/whatsapp-cloud-api/blob/main/LICENSE "License file") for more information. 264 | 265 | ## Disclaimer 266 | 267 | This package is not officially maintained by Facebook. WhatsApp and Facebook trademarks and logos are the property of Meta Platforms, Inc. 268 | -------------------------------------------------------------------------------- /banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asim-altayb/whatsapp-api-laravel/549c62d771b3918e9ff8d3dde072441025515e64/banner.webp -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asim-altayb/whatsapp-api-laravel", 3 | "version": "2.0.0", 4 | "description": "Help You to Deal with whatsapp Api Cloud.", 5 | "license": "MIT", 6 | "type": "library", 7 | "require": { 8 | "php": "^7.4 || ^8.0 || ^8.1", 9 | "guzzlehttp/guzzle": "^7.0", 10 | "illuminate/support": "^6.9|^7.0|^8.0|^9.0|^10.0", 11 | "myclabs/php-enum": "^1.8", 12 | "vlucas/phpdotenv": "^5.4" 13 | }, 14 | "minimum-stability": "stable", 15 | "prefer-stable": true, 16 | "config": { 17 | "sort-packages": true, 18 | "preferred-install": "dist", 19 | "optimize-autoloader": true 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "AsimAltayb\\WhatsappApiLaravel\\": "src/" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "AsimAltayb\\WhatsappApiLaravel\\": "src/" 29 | } 30 | }, 31 | "authors": [ 32 | { 33 | "name": "Asim", 34 | "email": "sudoasim@gmail.com" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | handler = $handler ?? $this->defaultHandler($graph_version); 34 | 35 | $this->graph_version = $graph_version; 36 | } 37 | 38 | /** 39 | * Return the original request that returned this response. 40 | * 41 | * @return Response Raw response from the server. 42 | * 43 | * @throws AsimAltayb\WhatsappApiLaravel\Response\ResponseException 44 | */ 45 | public function sendRequest(Request $request): Response 46 | { 47 | $raw_response = $this->handler->send( 48 | $this->buildRequestUri($request), 49 | $request->encodedBody(), 50 | $request->headers(), 51 | $request->timeout() 52 | ); 53 | 54 | $return_response = new Response( 55 | $request, 56 | $raw_response->body(), 57 | $raw_response->httpResponseCode(), 58 | $raw_response->headers() 59 | ); 60 | 61 | if ($return_response->isError()) { 62 | $return_response->throwException(); 63 | } 64 | 65 | return $return_response; 66 | } 67 | 68 | private function defaultHandler(): ClientHandler 69 | { 70 | return new GuzzleClientHandler(); 71 | } 72 | 73 | private function buildBaseUri(): string 74 | { 75 | return self::BASE_GRAPH_URL . '/' . $this->graph_version; 76 | } 77 | 78 | private function buildRequestUri(Request $request): string 79 | { 80 | return $this->buildBaseUri() . '/' . $request->fromPhoneNumberId() . '/messages'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Http/ClientHandler.php: -------------------------------------------------------------------------------- 1 | guzzle_client = $guzzle_client ?: new Client(); 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | * 25 | */ 26 | public function send(string $url, string $body, array $headers, int $timeout): RawResponse 27 | { 28 | $raw_handler_response = $this->guzzle_client->post($url, [ 29 | 'body' => $body, 30 | 'headers' => $headers, 31 | 'timeout' => $timeout, 32 | ]); 33 | 34 | return new RawResponse( 35 | $raw_handler_response->getHeaders(), 36 | $raw_handler_response->getBody(), 37 | $raw_handler_response->getStatusCode() 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Http/RawResponse.php: -------------------------------------------------------------------------------- 1 | http_response_code = (int)$http_status_code; 33 | } 34 | 35 | if (is_array($headers)) { 36 | $this->headers = $headers; 37 | } else { 38 | $this->setHeadersFromString($headers); 39 | } 40 | 41 | $this->body = $body; 42 | } 43 | 44 | /** 45 | * Return the response headers. 46 | * 47 | * @return array 48 | */ 49 | public function headers(): array 50 | { 51 | return $this->headers; 52 | } 53 | 54 | /** 55 | * Return the body of the response. 56 | * 57 | * @return string 58 | */ 59 | public function body(): string 60 | { 61 | return $this->body; 62 | } 63 | 64 | /** 65 | * Return the HTTP response code. 66 | * 67 | * @return int 68 | */ 69 | public function httpResponseCode(): int 70 | { 71 | return $this->http_response_code; 72 | } 73 | 74 | /** 75 | * Sets the HTTP response code from a raw header. 76 | * 77 | * @param string $raw_response_headers 78 | */ 79 | public function setHttpResponseCodeFromHeader($raw_response_headers) 80 | { 81 | // https://tools.ietf.org/html/rfc7230#section-3.1.2 82 | list($version, $status, $reason) = array_pad(explode(' ', $raw_response_headers, 3), 3, null); 83 | $this->http_response_code = (int) $status; 84 | } 85 | 86 | /** 87 | * Parse the raw headers and set as an array. 88 | * 89 | * @param string $raw_headers The raw headers from the response. 90 | */ 91 | protected function setHeadersFromString($raw_headers) 92 | { 93 | // Normalize line breaks 94 | $raw_headers = str_replace("\r\n", "\n", $raw_headers); 95 | 96 | // There will be multiple headers if a 301 was followed 97 | // or a proxy was followed, etc 98 | $header_collection = explode("\n\n", trim($raw_headers)); 99 | // We just want the last response (at the end) 100 | $raw_header = array_pop($header_collection); 101 | 102 | $header_components = explode("\n", $raw_header); 103 | foreach ($header_components as $line) { 104 | if (strpos($line, ': ') === false) { 105 | $this->setHttpResponseCodeFromHeader($line); 106 | } else { 107 | list($key, $value) = explode(': ', $line, 2); 108 | $this->headers[$key] = $value; 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Message/AudioMessage.php: -------------------------------------------------------------------------------- 1 | id = $id; 27 | 28 | parent::__construct($to); 29 | } 30 | 31 | public function identifierType(): string 32 | { 33 | return $this->id->type(); 34 | } 35 | 36 | public function identifierValue(): string 37 | { 38 | return $this->id->value(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Message/Contact/ContactName.php: -------------------------------------------------------------------------------- 1 | first_name = $first_name; 14 | $this->last_name = $last_name; 15 | } 16 | 17 | public function fullName(): string 18 | { 19 | return "$this->first_name $this->last_name"; 20 | } 21 | 22 | public function firstName(): string 23 | { 24 | return $this->first_name; 25 | } 26 | 27 | public function lastName(): string 28 | { 29 | return $this->last_name; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Message/Contact/Phone.php: -------------------------------------------------------------------------------- 1 | number = $number; 16 | $this->wa_id = $wa_id; 17 | $this->type = $type; 18 | } 19 | 20 | public function number(): string 21 | { 22 | return $this->number; 23 | } 24 | 25 | public function waId(): string 26 | { 27 | return $this->wa_id; 28 | } 29 | 30 | public function type(): PhoneType 31 | { 32 | return $this->type; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Message/Contact/PhoneType.php: -------------------------------------------------------------------------------- 1 | phones = $phones; 12 | } 13 | 14 | public function count(): int 15 | { 16 | return count($this->phones); 17 | } 18 | 19 | public function getIterator(): \ArrayIterator 20 | { 21 | return new \ArrayIterator($this->phones); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Message/ContactMessage.php: -------------------------------------------------------------------------------- 1 | name = $name; 26 | $this->phones = new Phones(...$phones); 27 | 28 | parent::__construct($to); 29 | } 30 | 31 | public function fullName(): string 32 | { 33 | return $this->name->fullName(); 34 | } 35 | 36 | public function firstName(): string 37 | { 38 | return $this->name->firstName(); 39 | } 40 | 41 | public function lastName(): string 42 | { 43 | return $this->name->lastName(); 44 | } 45 | 46 | public function phones(): Phones 47 | { 48 | return $this->phones; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Message/DocumentMessage.php: -------------------------------------------------------------------------------- 1 | id = $id; 37 | $this->name = $name; 38 | $this->caption = $caption; 39 | 40 | parent::__construct($to); 41 | } 42 | 43 | /** 44 | * Name of the document to show on a WhatsApp message. 45 | */ 46 | public function filename(): string 47 | { 48 | return $this->name; 49 | } 50 | 51 | public function caption(): ?string 52 | { 53 | return $this->caption; 54 | } 55 | 56 | public function identifierType(): string 57 | { 58 | return $this->id->type(); 59 | } 60 | 61 | public function identifierValue(): string 62 | { 63 | return $this->id->value(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Message/Error/InvalidMessage.php: -------------------------------------------------------------------------------- 1 | id = $id; 32 | $this->caption = $caption; 33 | 34 | parent::__construct($to); 35 | } 36 | 37 | public function caption(): ?string 38 | { 39 | return $this->caption; 40 | } 41 | 42 | public function identifierType(): string 43 | { 44 | return $this->id->type(); 45 | } 46 | 47 | public function identifierValue(): string 48 | { 49 | return $this->id->value(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Message/LocationMessage.php: -------------------------------------------------------------------------------- 1 | longitude = $longitude; 35 | $this->latitude = $latitude; 36 | $this->name = $name; 37 | $this->address = $address; 38 | 39 | parent::__construct($to); 40 | } 41 | 42 | public function longitude(): float 43 | { 44 | return $this->longitude; 45 | } 46 | 47 | public function latitude(): float 48 | { 49 | return $this->latitude; 50 | } 51 | 52 | public function name(): string 53 | { 54 | return $this->name; 55 | } 56 | 57 | public function address(): string 58 | { 59 | return $this->address; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Message/Media/LinkID.php: -------------------------------------------------------------------------------- 1 | value = $id; 20 | } 21 | 22 | public function type(): string 23 | { 24 | return $this->type; 25 | } 26 | 27 | public function value(): string 28 | { 29 | return $this->value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Message/Media/MediaObjectID.php: -------------------------------------------------------------------------------- 1 | to = $to; 35 | } 36 | 37 | /** 38 | * Return the WhatsApp ID or phone number for the person you want to send a message to. 39 | * 40 | * @return string 41 | */ 42 | public function to(): string 43 | { 44 | return $this->to; 45 | } 46 | 47 | /** 48 | * Return the type of message object. 49 | * 50 | * @return string 51 | */ 52 | public function type(): string 53 | { 54 | return $this->type; 55 | } 56 | 57 | /** 58 | * Return the messaging product. 59 | * 60 | * @return string 61 | */ 62 | public function messagingProduct(): string 63 | { 64 | return $this->messaging_product; 65 | } 66 | 67 | /** 68 | * Return the recipient type. 69 | * 70 | * @return string 71 | */ 72 | public function recipientType(): string 73 | { 74 | return $this->recipient_type; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Message/StickerMessage.php: -------------------------------------------------------------------------------- 1 | id = $id; 27 | 28 | parent::__construct($to); 29 | } 30 | 31 | public function identifierType(): string 32 | { 33 | return $this->id->type(); 34 | } 35 | 36 | public function identifierValue(): string 37 | { 38 | return $this->id->value(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Message/Template/Component.php: -------------------------------------------------------------------------------- 1 | header = $header; 25 | $this->body = $body; 26 | $this->buttons = $buttons; 27 | } 28 | 29 | public function header(): array 30 | { 31 | return $this->header; 32 | } 33 | 34 | public function body(): array 35 | { 36 | return $this->body; 37 | } 38 | 39 | public function buttons(): array 40 | { 41 | return $this->buttons; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Message/TemplateMessage.php: -------------------------------------------------------------------------------- 1 | name = $name; 37 | $this->language = $language; 38 | $this->components = $components; 39 | 40 | parent::__construct($to); 41 | } 42 | 43 | public function name(): string 44 | { 45 | return $this->name; 46 | } 47 | 48 | public function language(): string 49 | { 50 | return $this->language; 51 | } 52 | 53 | public function header(): array 54 | { 55 | return $this->components 56 | ? $this->components->header() 57 | : []; 58 | } 59 | 60 | public function body(): array 61 | { 62 | return $this->components 63 | ? $this->components->body() 64 | : []; 65 | } 66 | 67 | public function buttons(): array 68 | { 69 | return $this->components 70 | ? $this->components->buttons() 71 | : []; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Message/TextMessage.php: -------------------------------------------------------------------------------- 1 | assertTextIsValid($text); 37 | 38 | $this->text = $text; 39 | $this->preview_url = $preview_url; 40 | 41 | parent::__construct($to); 42 | } 43 | 44 | /** 45 | * Return the body of the text message. 46 | * 47 | * @return string 48 | */ 49 | public function text(): string 50 | { 51 | return $this->text; 52 | } 53 | 54 | /** 55 | * Return if preview box for URLs contained in the text message is shown. 56 | * 57 | * @return bool 58 | */ 59 | public function previewUrl(): bool 60 | { 61 | return $this->preview_url; 62 | } 63 | 64 | private function assertTextIsValid(string $text): void 65 | { 66 | if (strlen($text) > self::MAXIMUM_LENGTH) { 67 | throw new \LengthException('The maximun length for a message text is ' . self::MAXIMUM_LENGTH . ' characters'); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Message/VideoMessage.php: -------------------------------------------------------------------------------- 1 | id = $id; 32 | $this->caption = $caption; 33 | 34 | parent::__construct($to); 35 | } 36 | 37 | public function caption(): ?string 38 | { 39 | return $this->caption; 40 | } 41 | 42 | public function identifierType(): string 43 | { 44 | return $this->id->type(); 45 | } 46 | 47 | public function identifierValue(): string 48 | { 49 | return $this->id->value(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Providers/WhatsappServiceProvider.php: -------------------------------------------------------------------------------- 1 | message = $message; 59 | $this->access_token = $access_token; 60 | $this->from_phone_number_id = $from_phone_number_id; 61 | $this->timeout = $timeout ?? static::DEFAULT_REQUEST_TIMEOUT; 62 | 63 | $this->makeBody(); 64 | $this->encodeBody(); 65 | } 66 | 67 | /** 68 | * Returns the raw body of the request. 69 | * 70 | * @return array 71 | */ 72 | public function body(): array 73 | { 74 | return $this->body; 75 | } 76 | 77 | /** 78 | * Returns the body of the request encoded. 79 | * 80 | * @return string 81 | */ 82 | public function encodedBody(): string 83 | { 84 | return $this->encoded_body; 85 | } 86 | 87 | /** 88 | * Return the headers for this request. 89 | * 90 | * @return array 91 | */ 92 | public function headers(): array 93 | { 94 | return [ 95 | 'Authorization' => "Bearer $this->access_token", 96 | 'Content-Type' => 'application/json', 97 | ]; 98 | } 99 | 100 | /** 101 | * Return the access token for this request. 102 | * 103 | * @return string 104 | */ 105 | public function accessToken(): string 106 | { 107 | return $this->access_token; 108 | } 109 | 110 | /** 111 | * Return WhatsApp Number Id for this request. 112 | * 113 | * @return string 114 | */ 115 | public function fromPhoneNumberId(): string 116 | { 117 | return $this->from_phone_number_id; 118 | } 119 | 120 | /** 121 | * Return the timeout for this request. 122 | * 123 | * @return int 124 | */ 125 | public function timeout(): int 126 | { 127 | return $this->timeout; 128 | } 129 | 130 | /** 131 | * Makes the raw body of the request. 132 | * 133 | * @return array 134 | */ 135 | abstract protected function makeBody(): void; 136 | 137 | /** 138 | * Encodes the raw body of the request. 139 | * 140 | * @return array 141 | */ 142 | private function encodeBody(): void 143 | { 144 | $this->encoded_body = json_encode($this->body()); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Request/RequestAudioMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 16 | 'messaging_product' => $this->message->messagingProduct(), 17 | 'recipient_type' => $this->message->recipientType(), 18 | 'to' => $this->message->to(), 19 | 'type' => $this->message->type(), 20 | 'audio' => [ 21 | $this->message->identifierType() => $this->message->identifierValue(), 22 | ], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Request/RequestContactMessage.php: -------------------------------------------------------------------------------- 1 | message->type(); 16 | 17 | $this->body = [ 18 | 'messaging_product' => $this->message->messagingProduct(), 19 | 'recipient_type' => $this->message->recipientType(), 20 | 'to' => $this->message->to(), 21 | 'type' => $this->message->type(), 22 | $message_type => [ 23 | [ 24 | 'name' => [ 25 | 'formatted_name' => $this->message->fullName(), 26 | 'first_name' => $this->message->firstName(), 27 | 'last_name' => $this->message->lastName(), 28 | ], 29 | ], 30 | ], 31 | ]; 32 | 33 | foreach ($this->message->phones() as $phone) { 34 | $phone_array = [ 35 | 'phone' => $phone->number(), 36 | 'type' => $phone->type()->getValue(), 37 | ]; 38 | 39 | if (!empty($phone->waId())) { 40 | $phone_array['wa_id'] = $phone->waId(); 41 | } 42 | 43 | $this->body[$message_type][0]['phones'][] = $phone_array; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Request/RequestDocumentMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 16 | 'messaging_product' => $this->message->messagingProduct(), 17 | 'recipient_type' => $this->message->recipientType(), 18 | 'to' => $this->message->to(), 19 | 'type' => $this->message->type(), 20 | 'document' => [ 21 | 'caption' => $this->message->caption(), 22 | 'filename' => $this->message->filename(), 23 | $this->message->identifierType() => $this->message->identifierValue(), 24 | ], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Request/RequestImageMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 16 | 'messaging_product' => $this->message->messagingProduct(), 17 | 'recipient_type' => $this->message->recipientType(), 18 | 'to' => $this->message->to(), 19 | 'type' => $this->message->type(), 20 | 'image' => [ 21 | 'caption' => $this->message->caption(), 22 | $this->message->identifierType() => $this->message->identifierValue(), 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Request/RequestLocationMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 16 | 'messaging_product' => $this->message->messagingProduct(), 17 | 'recipient_type' => $this->message->recipientType(), 18 | 'to' => $this->message->to(), 19 | 'type' => $this->message->type(), 20 | $this->message->type() => [ 21 | 'longitude' => $this->message->longitude(), 22 | 'latitude' => $this->message->latitude(), 23 | 'name' => $this->message->name(), 24 | 'address' => $this->message->address(), 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Request/RequestStickerMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 16 | 'messaging_product' => $this->message->messagingProduct(), 17 | 'recipient_type' => $this->message->recipientType(), 18 | 'to' => $this->message->to(), 19 | 'type' => $this->message->type(), 20 | $this->message->type() => [ 21 | $this->message->identifierType() => $this->message->identifierValue(), 22 | ], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Request/RequestTemplateMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 15 | 'messaging_product' => $this->message->messagingProduct(), 16 | 'recipient_type' => $this->message->recipientType(), 17 | 'to' => $this->message->to(), 18 | 'type' => $this->message->type(), 19 | 'template' => [ 20 | 'name' => $this->message->name(), 21 | 'language' => ['code' => $this->message->language()], 22 | 'components' => [], 23 | ], 24 | ]; 25 | 26 | if ($this->message->header()) { 27 | $this->body['template']['components'][] = [ 28 | 'type' => 'header', 29 | 'parameters' => $this->message->header(), 30 | ]; 31 | } 32 | 33 | if ($this->message->body()) { 34 | $this->body['template']['components'][] = [ 35 | 'type' => 'body', 36 | 'parameters' => $this->message->body(), 37 | ]; 38 | } 39 | 40 | foreach ($this->message->buttons() as $button) { 41 | $this->body['template']['components'][] = $button; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Request/RequestTextMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 16 | 'messaging_product' => $this->message->messagingProduct(), 17 | 'recipient_type' => $this->message->recipientType(), 18 | 'to' => $this->message->to(), 19 | 'type' => $this->message->type(), 20 | 'text' => [ 21 | 'preview_url' => $this->message->previewUrl(), 22 | 'body' => $this->message->text(), 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Request/RequestVideoMessage.php: -------------------------------------------------------------------------------- 1 | body = [ 16 | 'messaging_product' => $this->message->messagingProduct(), 17 | 'recipient_type' => $this->message->recipientType(), 18 | 'to' => $this->message->to(), 19 | 'type' => $this->message->type(), 20 | $this->message->type() => [ 21 | $this->message->identifierType() => $this->message->identifierValue(), 22 | 'caption' => $this->message->caption(), 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | request = $request; 45 | $this->body = $body; 46 | $this->http_status_code = $http_status_code; 47 | $this->headers = $headers; 48 | 49 | $this->decodeBody(); 50 | } 51 | 52 | /** 53 | * Return the original request that returned this response. 54 | * 55 | * @return Resquest 56 | */ 57 | public function request() 58 | { 59 | return $this->request; 60 | } 61 | 62 | /** 63 | * Return the access token that was used for this response. 64 | * 65 | * @return string 66 | */ 67 | public function accessToken() 68 | { 69 | return $this->request->accessToken(); 70 | } 71 | 72 | /** 73 | * Return the HTTP status code for this response. 74 | * 75 | * @return int 76 | */ 77 | public function httpStatusCode() 78 | { 79 | return $this->http_status_code; 80 | } 81 | 82 | /** 83 | * Return the HTTP headers for this response. 84 | * 85 | * @return array 86 | */ 87 | public function headers() 88 | { 89 | return $this->headers; 90 | } 91 | 92 | /** 93 | * Return the raw body response. 94 | * 95 | * @return string 96 | */ 97 | public function body() 98 | { 99 | return $this->body; 100 | } 101 | 102 | /** 103 | * Return the decoded body response. 104 | * 105 | * @return array 106 | */ 107 | public function decodedBody() 108 | { 109 | return $this->decoded_body; 110 | } 111 | 112 | /** 113 | * Get the version of Graph that returned this response. 114 | * 115 | * @return string|null 116 | */ 117 | public function graphVersion() 118 | { 119 | return $this->headers['facebook-api-version'] ?? null; 120 | } 121 | 122 | /** 123 | * Returns true if Graph returned an error message. 124 | * 125 | * @return bool 126 | */ 127 | public function isError() 128 | { 129 | return isset($this->decoded_body['error']); 130 | } 131 | 132 | /** 133 | * Throws the exception. 134 | * 135 | * @throws ResponseException 136 | */ 137 | public function throwException() 138 | { 139 | throw new ResponseException($this); 140 | } 141 | 142 | /** 143 | * Convert the raw response into an array if possible. 144 | * 145 | * Graph will return 2 types of responses: 146 | * - JSON(P) 147 | * Most responses from Graph are JSON(P) 148 | * - application/x-www-form-urlencoded key/value pairs 149 | * Happens on the `/oauth/access_token` endpoint when exchanging 150 | * a short-lived access token for a long-lived access token 151 | * - And sometimes nothing :/ but that'd be a bug. 152 | */ 153 | public function decodeBody() 154 | { 155 | $this->decoded_body = json_decode($this->body, true); 156 | 157 | if ($this->decoded_body === null) { 158 | $this->decoded_body = []; 159 | parse_str($this->body, $this->decoded_body); 160 | } elseif (is_numeric($this->decoded_body)) { 161 | $this->decoded_body = ['id' => $this->decoded_body]; 162 | } 163 | 164 | if (!is_array($this->decoded_body)) { 165 | $this->decoded_body = []; 166 | } 167 | 168 | if ($this->isError()) { 169 | $this->throwException(); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Response/ResponseException.php: -------------------------------------------------------------------------------- 1 | response = $response; 27 | $this->response_data = $response->decodedBody(); 28 | } 29 | 30 | /** 31 | * Returns the HTTP status code 32 | * 33 | * @return int 34 | */ 35 | public function httpStatusCode() 36 | { 37 | return $this->response->httpStatusCode(); 38 | } 39 | 40 | /** 41 | * Returns the raw response used to create the exception. 42 | * 43 | * @return string 44 | */ 45 | public function rawResponse() 46 | { 47 | return $this->response->body(); 48 | } 49 | 50 | /** 51 | * Returns the decoded response used to create the exception. 52 | * 53 | * @return array 54 | */ 55 | public function responseData() 56 | { 57 | return $this->response_data; 58 | } 59 | 60 | /** 61 | * Returns the response entity used to create the exception. 62 | * 63 | * @return Response 64 | */ 65 | public function response() 66 | { 67 | return $this->response; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/WhatsAppCloudApi.php: -------------------------------------------------------------------------------- 1 | null, 61 | 'access_token' => '', 62 | 'graph_version' => static::DEFAULT_GRAPH_VERSION, 63 | 'client_handler' => null, 64 | 'timeout' => null, 65 | ], $config); 66 | 67 | $this->app = new WhatsAppCloudApiApp($config['from_phone_number_id'], $config['access_token']); 68 | $this->timeout = $config['timeout']; 69 | $this->client = new Client($config['graph_version'], $config['client_handler']); 70 | } 71 | 72 | /** 73 | * Sends a Whatsapp text message. 74 | * 75 | * @param string WhatsApp ID or phone number for the person you want to send a message to. 76 | * @param string The body of the text message. 77 | * @param bool Determines if show a preview box for URLs contained in the text message. 78 | * 79 | * @throws Response\ResponseException 80 | */ 81 | public function sendTextMessage(string $to, string $text, bool $preview_url = false): Response 82 | { 83 | $message = new TextMessage($to, $text, $preview_url); 84 | $request = new RequestTextMessage( 85 | $message, 86 | $this->app->accessToken(), 87 | $this->app->fromPhoneNumberId(), 88 | $this->timeout 89 | ); 90 | 91 | return $this->client->sendRequest($request); 92 | } 93 | 94 | /** 95 | * Sends a document uploaded to the WhatsApp Cloud servers by it Media ID or you also 96 | * can put any public URL of some document uploaded on Internet. 97 | * 98 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 99 | * @param Document $document Document to send. See documents accepted in the Message/Document folder. 100 | * @return Response 101 | * 102 | * @throws Response\ResponseException 103 | */ 104 | public function sendDocument(string $to, MediaID $document_id, string $name, ?string $caption): Response 105 | { 106 | $message = new DocumentMessage($to, $document_id, $name, $caption); 107 | $request = new RequestDocumentMessage( 108 | $message, 109 | $this->app->accessToken(), 110 | $this->app->fromPhoneNumberId(), 111 | $this->timeout 112 | ); 113 | 114 | return $this->client->sendRequest($request); 115 | } 116 | 117 | /** 118 | * Sends a message template. 119 | * 120 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 121 | * @param string $template_name Name of the template to send. 122 | * @param string $language Language code 123 | * @param Component|null $component Component parameters of a template 124 | * 125 | * @link https://developers.facebook.com/docs/whatsapp/api/messages/message-templates#supported-languages See language codes supported. 126 | * @return Response 127 | * 128 | * @throws Response\ResponseException 129 | */ 130 | public function sendTemplate(string $to, string $template_name, string $language = 'en_US', ?Component $components = null): Response 131 | { 132 | $message = new TemplateMessage($to, $template_name, $language, $components); 133 | $request = new RequestTemplateMessage( 134 | $message, 135 | $this->app->accessToken(), 136 | $this->app->fromPhoneNumberId(), 137 | $this->timeout 138 | ); 139 | 140 | return $this->client->sendRequest($request); 141 | } 142 | 143 | /** 144 | * Sends a document uploaded to the WhatsApp Cloud servers by it Media ID or you also 145 | * can put any public URL of some document uploaded on Internet. 146 | * 147 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 148 | * @param MediaId $document_id WhatsApp Media ID or any Internet public link document. 149 | * @return Response 150 | * 151 | * @throws Response\ResponseException 152 | */ 153 | public function sendAudio(string $to, MediaID $document_id): Response 154 | { 155 | $message = new AudioMessage($to, $document_id); 156 | $request = new RequestAudioMessage( 157 | $message, 158 | $this->app->accessToken(), 159 | $this->app->fromPhoneNumberId(), 160 | $this->timeout 161 | ); 162 | 163 | return $this->client->sendRequest($request); 164 | } 165 | 166 | /** 167 | * Sends a document uploaded to the WhatsApp Cloud servers by it Media ID or you also 168 | * can put any public URL of some document uploaded on Internet. 169 | * 170 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 171 | * @param string $caption Description of the specified image file. 172 | * @param MediaId $document_id WhatsApp Media ID or any Internet public link document. 173 | * @return Response 174 | * 175 | * @throws Response\ResponseException 176 | */ 177 | public function sendImage(string $to, MediaID $document_id, ?string $caption = ''): Response 178 | { 179 | $message = new ImageMessage($to, $document_id, $caption); 180 | $request = new RequestImageMessage( 181 | $message, 182 | $this->app->accessToken(), 183 | $this->app->fromPhoneNumberId(), 184 | $this->timeout 185 | ); 186 | 187 | return $this->client->sendRequest($request); 188 | } 189 | 190 | /** 191 | * Sends a document uploaded to the WhatsApp Cloud servers by it Media ID or you also 192 | * can put any public URL of some document uploaded on Internet. 193 | * 194 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 195 | * @param MediaId $document_id WhatsApp Media ID or any Internet public link document. 196 | * @return Response 197 | * 198 | * @throws Response\ResponseException 199 | */ 200 | public function sendVideo(string $to, MediaID $link, string $caption = ''): Response 201 | { 202 | $message = new VideoMessage($to, $link, $caption); 203 | $request = new RequestVideoMessage( 204 | $message, 205 | $this->app->accessToken(), 206 | $this->app->fromPhoneNumberId(), 207 | $this->timeout 208 | ); 209 | 210 | return $this->client->sendRequest($request); 211 | } 212 | 213 | /** 214 | * Sends a sticker uploaded to the WhatsApp Cloud servers by it Media ID or you also 215 | * can put any public URL of some document uploaded on Internet. 216 | * 217 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 218 | * @param MediaId $document_id WhatsApp Media ID or any Internet public link document. 219 | * @return Response 220 | * 221 | * @throws Response\ResponseException 222 | */ 223 | public function sendSticker(string $to, MediaID $link): Response 224 | { 225 | $message = new StickerMessage($to, $link); 226 | $request = new RequestStickerMessage( 227 | $message, 228 | $this->app->accessToken(), 229 | $this->app->fromPhoneNumberId(), 230 | $this->timeout 231 | ); 232 | 233 | return $this->client->sendRequest($request); 234 | } 235 | 236 | /** 237 | * Sends a location 238 | * 239 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 240 | * @param float $longitude Longitude position. 241 | * @param float $latitude Latitude position. 242 | * @param string $name Name of location sent. 243 | * @param address $address Address of location sent. 244 | * 245 | * @return Response 246 | * 247 | * @throws Response\ResponseException 248 | */ 249 | public function sendLocation(string $to, float $longitude, float $latitude, string $name = '', string $address = ''): Response 250 | { 251 | $message = new LocationMessage($to, $longitude, $latitude, $name, $address); 252 | $request = new RequestLocationMessage( 253 | $message, 254 | $this->app->accessToken(), 255 | $this->app->fromPhoneNumberId(), 256 | $this->timeout 257 | ); 258 | 259 | return $this->client->sendRequest($request); 260 | } 261 | 262 | /** 263 | * Sends a contact 264 | * 265 | * @param string $to WhatsApp ID or phone number for the person you want to send a message to. 266 | * @param ContactName $name The contact name object. 267 | * @param Phone|null $phone The contact phone number. 268 | * 269 | * @return Response 270 | * 271 | * @throws Response\ResponseException 272 | */ 273 | public function sendContact(string $to, ContactName $name, Phone ...$phone): Response 274 | { 275 | $message = new ContactMessage($to, $name, ...$phone); 276 | $request = new RequestContactMessage( 277 | $message, 278 | $this->app->accessToken(), 279 | $this->app->fromPhoneNumberId(), 280 | $this->timeout 281 | ); 282 | 283 | return $this->client->sendRequest($request); 284 | } 285 | 286 | /** 287 | * Returns the Facebook Whatsapp Access Token. 288 | * 289 | * @return string 290 | */ 291 | public function accessToken(): string 292 | { 293 | return $this->app->accessToken(); 294 | } 295 | 296 | /** 297 | * Returns the Facebook Phone Number ID. 298 | * 299 | * @return string 300 | */ 301 | public function fromPhoneNumberId(): string 302 | { 303 | return $this->app->fromPhoneNumberId(); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/WhatsAppCloudApiApp.php: -------------------------------------------------------------------------------- 1 | loadEnv(); 37 | 38 | $this->from_phone_number_id = $from_phone_number_id ?: $_ENV[static::APP_FROM_PHONE_NUMBER_ENV_NAME] ?? null; 39 | $this->access_token = $access_token ?: $_ENV[static::APP_TOKEN_ENV_NAME] ?? null; 40 | 41 | $this->validate($this->from_phone_number_id, $this->access_token); 42 | } 43 | 44 | /** 45 | * Returns the Facebook Whatsapp Access Token. 46 | * 47 | * @return string 48 | */ 49 | public function accessToken(): string 50 | { 51 | return $this->access_token; 52 | } 53 | 54 | /** 55 | * Returns the Facebook Phone Number ID. 56 | * 57 | * @return string 58 | */ 59 | public function fromPhoneNumberId(): string 60 | { 61 | return $this->from_phone_number_id; 62 | } 63 | 64 | private function validate(string $from_phone_number_id, string $access_token): void 65 | { 66 | // validate by function type hinting 67 | } 68 | 69 | private function loadEnv(): void 70 | { 71 | $dotenv = \Dotenv\Dotenv::createImmutable(__DIR__); 72 | $dotenv->safeLoad(); 73 | } 74 | } 75 | --------------------------------------------------------------------------------