From 358596c47bc96c36436b9cf820fc28200878c5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Albarca?= Date: Tue, 14 Apr 2026 18:39:16 +0200 Subject: [PATCH] Add BSUID support --- README.md | 26 +- src/Message/AudioMessage.php | 4 +- src/Message/ButtonReplyMessage.php | 4 +- src/Message/CatalogMessage.php | 4 +- src/Message/ContactMessage.php | 4 +- src/Message/CtaUrlMessage.php | 7 +- src/Message/DocumentMessage.php | 4 +- src/Message/ImageMessage.php | 4 +- src/Message/LocationMessage.php | 4 +- src/Message/LocationRequestMessage.php | 4 +- src/Message/Message.php | 27 +- src/Message/MultiProductMessage.php | 4 +- src/Message/OptionsListMessage.php | 4 +- src/Message/ReactionMessage.php | 4 +- src/Message/SingleProductMessage.php | 4 +- src/Message/StickerMessage.php | 4 +- src/Message/TemplateMessage.php | 4 +- src/Message/TextMessage.php | 6 +- src/Message/VideoMessage.php | 4 +- src/Request/MessageRequest.php | 27 ++ .../MessageRequest/RequestAudioMessage.php | 16 +- .../RequestButtonReplyMessage.php | 20 +- .../MessageRequest/RequestCatalogMessage.php | 22 +- .../MessageRequest/RequestContactMessage.php | 24 +- .../MessageRequest/RequestCtaUrlMessage.php | 18 +- .../MessageRequest/RequestDocumentMessage.php | 20 +- .../MessageRequest/RequestImageMessage.php | 18 +- .../MessageRequest/RequestLocationMessage.php | 22 +- .../RequestLocationRequestMessage.php | 22 +- .../RequestMultiProductMessage.php | 26 +- .../RequestOptionsListMessage.php | 20 +- .../MessageRequest/RequestReactionMessage.php | 14 +- .../RequestSingleProductMessage.php | 22 +- .../MessageRequest/RequestStickerMessage.php | 16 +- .../MessageRequest/RequestTemplateMessage.php | 20 +- .../MessageRequest/RequestTextMessage.php | 18 +- .../MessageRequest/RequestVideoMessage.php | 18 +- .../MessageNotificationFactory.php | 7 +- src/WebHook/Notification/Support/Customer.php | 43 ++- .../Notification/Support/CustomerIdType.php | 20 ++ src/WhatsAppCloudApi.php | 121 +++---- tests/Integration/WhatsAppCloudApiTest.php | 2 + .../Unit/WebHook/NotificationFactoryTest.php | 85 +++++ tests/Unit/WhatsAppCloudApiTest.php | 295 +++++++++++++++++- 44 files changed, 713 insertions(+), 349 deletions(-) create mode 100644 src/WebHook/Notification/Support/CustomerIdType.php diff --git a/README.md b/README.md index 81d2fd9..037ad76 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,30 @@ $whatsapp_cloud_api = new WhatsAppCloudApi([ $whatsapp_cloud_api->sendTextMessage('34676104574', 'Hey there! I\'m using WhatsApp Cloud API. Visit https://www.netflie.es'); ``` +### Send a message using BSUID recipient +```php +sendTextMessage(null, 'Hello from BSUID', false, $recipient_bsuid); + +// Send both values in the same request (WhatsApp Cloud API gives precedence to `to`) +$whatsapp_cloud_api->sendTextMessage('34676104574', 'Hello with both identifiers', false, $recipient_bsuid); +``` + +The same optional `$recipient` parameter is available in all `send*` methods that send messages to `/messages`. + +For `sendContact`, pass the BSUID before the phone entries: + +```php +$contact_name = new \Netflie\WhatsAppCloudApi\Message\Contact\ContactName('John', 'Doe'); +$phone = new \Netflie\WhatsAppCloudApi\Message\Contact\Phone('+14155550100', \Netflie\WhatsAppCloudApi\Message\Contact\PhoneType::CELL()); + +$whatsapp_cloud_api->sendContact(null, $contact_name, $recipient_bsuid, $phone); +``` + ### Send a document 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. @@ -200,7 +224,7 @@ use Netflie\WhatsAppCloudApi\Message\Contact\PhoneType; $name = new ContactName('Adams', 'Smith'); $phone = new Phone('34676204577', PhoneType::CELL()); -$whatsapp_cloud_api->sendContact('', $name, $phone); +$whatsapp_cloud_api->sendContact('', $name, null, $phone); ``` ### Send a list message diff --git a/src/Message/AudioMessage.php b/src/Message/AudioMessage.php index d64e455..8f62b60 100644 --- a/src/Message/AudioMessage.php +++ b/src/Message/AudioMessage.php @@ -21,11 +21,11 @@ final class AudioMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, MediaID $id, ?string $reply_to) + public function __construct(?string $to, MediaID $id, ?string $recipient, ?string $reply_to) { $this->id = $id; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function identifierType(): string diff --git a/src/Message/ButtonReplyMessage.php b/src/Message/ButtonReplyMessage.php index 0053456..489ca9e 100644 --- a/src/Message/ButtonReplyMessage.php +++ b/src/Message/ButtonReplyMessage.php @@ -16,14 +16,14 @@ class ButtonReplyMessage extends Message private ButtonAction $action; - public function __construct(string $to, string $body, ButtonAction $action, ?string $header = null, ?string $footer = null, ?string $reply_to = null) + public function __construct(?string $to, string $body, ButtonAction $action, ?string $header = null, ?string $footer = null, ?string $reply_to = null, ?string $recipient = null) { $this->body = $body; $this->action = $action; $this->header = $header; $this->footer = $footer; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function header(): ?string diff --git a/src/Message/CatalogMessage.php b/src/Message/CatalogMessage.php index 08504ae..4edc46f 100644 --- a/src/Message/CatalogMessage.php +++ b/src/Message/CatalogMessage.php @@ -18,13 +18,13 @@ final class CatalogMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, string $body, ?string $footer, ?string $thumbnail_product_retailer_id, ?string $reply_to) + public function __construct(?string $to, string $body, ?string $footer, ?string $thumbnail_product_retailer_id, ?string $reply_to, ?string $recipient = null) { $this->body = $body; $this->footer = $footer; $this->thumbnail_product_retailer_id = $thumbnail_product_retailer_id; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function body(): string diff --git a/src/Message/ContactMessage.php b/src/Message/ContactMessage.php index 1ac387d..98c04c9 100644 --- a/src/Message/ContactMessage.php +++ b/src/Message/ContactMessage.php @@ -20,12 +20,12 @@ final class ContactMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, ContactName $name, ?string $reply_to, Phone ...$phones) + public function __construct(?string $to, ContactName $name, ?string $reply_to, ?string $recipient = null, Phone ...$phones) { $this->name = $name; $this->phones = new Phones(...$phones); - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function fullName(): string diff --git a/src/Message/CtaUrlMessage.php b/src/Message/CtaUrlMessage.php index 335cfed..9142fa4 100644 --- a/src/Message/CtaUrlMessage.php +++ b/src/Message/CtaUrlMessage.php @@ -25,13 +25,14 @@ final class CtaUrlMessage extends Message * {@inheritdoc} */ public function __construct( - string $to, + ?string $to, string $displayText, string $url, ?Header $header, ?string $body, ?string $footer, - ?string $reply_to + ?string $reply_to, + ?string $recipient = null ) { $this->displayText = $displayText; $this->url = $url; @@ -39,7 +40,7 @@ public function __construct( $this->body = $body; $this->footer = $footer; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function getDisplayText(): string diff --git a/src/Message/DocumentMessage.php b/src/Message/DocumentMessage.php index 0e1500d..3c3ec0d 100644 --- a/src/Message/DocumentMessage.php +++ b/src/Message/DocumentMessage.php @@ -31,13 +31,13 @@ final class DocumentMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, MediaID $id, string $name, ?string $caption, ?string $reply_to) + public function __construct(?string $to, MediaID $id, string $name, ?string $caption, ?string $reply_to, ?string $recipient = null) { $this->id = $id; $this->name = $name; $this->caption = $caption; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } /** diff --git a/src/Message/ImageMessage.php b/src/Message/ImageMessage.php index 727c6cf..e2be038 100644 --- a/src/Message/ImageMessage.php +++ b/src/Message/ImageMessage.php @@ -26,12 +26,12 @@ final class ImageMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, MediaID $id, ?string $caption = '', ?string $reply_to = null) + public function __construct(?string $to, MediaID $id, ?string $caption = '', ?string $reply_to = null, ?string $recipient = null) { $this->id = $id; $this->caption = $caption; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function caption(): ?string diff --git a/src/Message/LocationMessage.php b/src/Message/LocationMessage.php index 8a64758..1d0cdff 100644 --- a/src/Message/LocationMessage.php +++ b/src/Message/LocationMessage.php @@ -25,7 +25,7 @@ final class LocationMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, float $longitude, float $latitude, string $name = '', string $address = '', ?string $reply_to = null) + public function __construct(?string $to, float $longitude, float $latitude, string $name = '', string $address = '', ?string $reply_to = null, ?string $recipient = null) { if ($address && !$name) { throw new InvalidMessage('Name is required.'); @@ -36,7 +36,7 @@ public function __construct(string $to, float $longitude, float $latitude, strin $this->name = $name; $this->address = $address; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function longitude(): float diff --git a/src/Message/LocationRequestMessage.php b/src/Message/LocationRequestMessage.php index 2e28058..d5ddd17 100644 --- a/src/Message/LocationRequestMessage.php +++ b/src/Message/LocationRequestMessage.php @@ -14,11 +14,11 @@ final class LocationRequestMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, string $body, ?string $reply_to) + public function __construct(?string $to, string $body, ?string $reply_to, ?string $recipient = null) { $this->body = $body; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function body(): string diff --git a/src/Message/Message.php b/src/Message/Message.php index 06f4bd1..430dfbf 100644 --- a/src/Message/Message.php +++ b/src/Message/Message.php @@ -2,6 +2,8 @@ namespace Netflie\WhatsAppCloudApi\Message; +use Netflie\WhatsAppCloudApi\Message\Error\InvalidMessage; + abstract class Message { /** @@ -20,9 +22,14 @@ abstract class Message private string $recipient_type = 'individual'; /** - * @var string WhatsApp ID or phone number for the person you want to send a message to. + * @var string|null WhatsApp ID or phone number for the person you want to send a message to. */ - private string $to; + private ?string $to; + + /** + * @var string|null Business-scoped user id (BSUID) recipient. + */ + private ?string $recipient; /** * The WhatsApp Message ID to reply to. @@ -32,22 +39,32 @@ abstract class Message /** * Creates a new Message class. */ - public function __construct(string $to, ?string $reply_to) + public function __construct(?string $to, ?string $recipient, ?string $reply_to) { $this->to = $to; + $this->recipient = $recipient; $this->reply_to = $reply_to; + + if (empty($this->to) && empty($this->recipient)) { + throw new InvalidMessage('Either `to` or `recipient` is required.'); + } } /** * Return the WhatsApp ID or phone number for the person you want to send a message to. * - * @return string + * @return string|null */ - public function to(): string + public function to(): ?string { return $this->to; } + public function recipient(): ?string + { + return $this->recipient; + } + /** * Return the type of message object. * diff --git a/src/Message/MultiProductMessage.php b/src/Message/MultiProductMessage.php index c42e8e7..3b1ea13 100644 --- a/src/Message/MultiProductMessage.php +++ b/src/Message/MultiProductMessage.php @@ -24,7 +24,7 @@ final class MultiProductMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, int $catalog_id, Action $action, string $header, string $body, ?string $footer, ?string $reply_to) + public function __construct(?string $to, int $catalog_id, Action $action, string $header, string $body, ?string $footer, ?string $reply_to, ?string $recipient = null) { $this->header = $header; $this->body = $body; @@ -32,7 +32,7 @@ public function __construct(string $to, int $catalog_id, Action $action, string $this->footer = $footer; $this->action = $action; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function header(): array diff --git a/src/Message/OptionsListMessage.php b/src/Message/OptionsListMessage.php index 8bb5ae5..da2c2bc 100644 --- a/src/Message/OptionsListMessage.php +++ b/src/Message/OptionsListMessage.php @@ -22,14 +22,14 @@ final class OptionsListMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, string $header, string $body, string $footer, Action $action, ?string $reply_to) + public function __construct(?string $to, string $header, string $body, string $footer, Action $action, ?string $reply_to, ?string $recipient = null) { $this->header = $header; $this->body = $body; $this->footer = $footer; $this->action = $action; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function header(): string diff --git a/src/Message/ReactionMessage.php b/src/Message/ReactionMessage.php index 46e3e21..81cc8a1 100644 --- a/src/Message/ReactionMessage.php +++ b/src/Message/ReactionMessage.php @@ -16,12 +16,12 @@ final class ReactionMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, string $message_id, string $emoji) + public function __construct(?string $to, string $message_id, string $emoji, ?string $recipient = null) { $this->emoji = $emoji; $this->message_id = $message_id; - parent::__construct($to, null); + parent::__construct($to, $recipient, null); } public function emoji(): string diff --git a/src/Message/SingleProductMessage.php b/src/Message/SingleProductMessage.php index 2e07693..1d0d1ba 100644 --- a/src/Message/SingleProductMessage.php +++ b/src/Message/SingleProductMessage.php @@ -20,14 +20,14 @@ final class SingleProductMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, int $catalog_id, string $product_retailer_id, ?string $body, ?string $footer, ?string $reply_to) + public function __construct(?string $to, int $catalog_id, string $product_retailer_id, ?string $body, ?string $footer, ?string $reply_to, ?string $recipient = null) { $this->catalog_id = $catalog_id; $this->product_retailer_id = $product_retailer_id; $this->body = $body; $this->footer = $footer; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function catalog_id(): int diff --git a/src/Message/StickerMessage.php b/src/Message/StickerMessage.php index 5bd16e0..7e852ae 100644 --- a/src/Message/StickerMessage.php +++ b/src/Message/StickerMessage.php @@ -21,11 +21,11 @@ final class StickerMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, MediaID $id, ?string $reply_to) + public function __construct(?string $to, MediaID $id, ?string $reply_to, ?string $recipient = null) { $this->id = $id; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function identifierType(): string diff --git a/src/Message/TemplateMessage.php b/src/Message/TemplateMessage.php index f38f178..fb55be2 100644 --- a/src/Message/TemplateMessage.php +++ b/src/Message/TemplateMessage.php @@ -31,13 +31,13 @@ final class TemplateMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, string $name, string $language = 'en_US', ?Component $components = null, ?string $reply_to = null) + public function __construct(?string $to, string $name, string $language = 'en_US', ?Component $components = null, ?string $reply_to = null, ?string $recipient = null) { $this->name = $name; $this->language = $language; $this->components = $components; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function name(): string diff --git a/src/Message/TextMessage.php b/src/Message/TextMessage.php index 560f82d..fd00b0b 100644 --- a/src/Message/TextMessage.php +++ b/src/Message/TextMessage.php @@ -27,18 +27,18 @@ final class TextMessage extends Message /** * Creates a new message of type text. * - * @param string $to + * @param ?string $to * @param string $text * @param bool $preview_url */ - public function __construct(string $to, string $text, bool $preview_url = false, ?string $reply_to = null) + public function __construct(?string $to, string $text, bool $preview_url = false, ?string $reply_to = null, ?string $recipient = null) { $this->assertTextIsValid($text); $this->text = $text; $this->preview_url = $preview_url; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } /** diff --git a/src/Message/VideoMessage.php b/src/Message/VideoMessage.php index 76653b5..2819824 100644 --- a/src/Message/VideoMessage.php +++ b/src/Message/VideoMessage.php @@ -26,12 +26,12 @@ final class VideoMessage extends Message /** * {@inheritdoc} */ - public function __construct(string $to, MediaID $id, ?string $caption = '', ?string $reply_to = null) + public function __construct(?string $to, MediaID $id, ?string $caption = '', ?string $reply_to = null, ?string $recipient = null) { $this->id = $id; $this->caption = $caption; - parent::__construct($to, $reply_to); + parent::__construct($to, $recipient, $reply_to); } public function caption(): ?string diff --git a/src/Request/MessageRequest.php b/src/Request/MessageRequest.php index 7f0eb05..8ecd61e 100644 --- a/src/Request/MessageRequest.php +++ b/src/Request/MessageRequest.php @@ -44,4 +44,31 @@ public function nodePath(): string { return $this->from_phone_number_id . '/messages'; } + + public function body(): array + { + return $this->build(); + } + + protected function build(): array + { + $body = [ + 'messaging_product' => $this->message->messagingProduct(), + 'recipient_type' => $this->message->recipientType(), + ]; + + if (!empty($this->message->to())) { + $body['to'] = $this->message->to(); + } + + if (!empty($this->message->recipient())) { + $body['recipient'] = $this->message->recipient(); + } + + if ($this->message->replyTo()) { + $body['context']['message_id'] = $this->message->replyTo(); + } + + return $body; + } } diff --git a/src/Request/MessageRequest/RequestAudioMessage.php b/src/Request/MessageRequest/RequestAudioMessage.php index f773a41..a3f8dd2 100644 --- a/src/Request/MessageRequest/RequestAudioMessage.php +++ b/src/Request/MessageRequest/RequestAudioMessage.php @@ -11,20 +11,12 @@ final class RequestAudioMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - 'audio' => [ - $this->message->identifierType() => $this->message->identifierValue(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body['audio'] = [ + $this->message->identifierType() => $this->message->identifierValue(), ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestButtonReplyMessage.php b/src/Request/MessageRequest/RequestButtonReplyMessage.php index ab3dc6b..98116a1 100644 --- a/src/Request/MessageRequest/RequestButtonReplyMessage.php +++ b/src/Request/MessageRequest/RequestButtonReplyMessage.php @@ -8,16 +8,12 @@ class RequestButtonReplyMessage extends MessageRequest { public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => 'interactive', - 'interactive' => [ - 'type' => 'button', - 'body' => ['text' => $this->message->body()], - 'action' => ['buttons' => $this->message->action()->buttons()], - ], + $body = parent::body(); + $body['type'] = 'interactive'; + $body['interactive'] = [ + 'type' => 'button', + 'body' => ['text' => $this->message->body()], + 'action' => ['buttons' => $this->message->action()->buttons()], ]; if ($this->message->header()) { @@ -33,10 +29,6 @@ public function body(): array ]; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestCatalogMessage.php b/src/Request/MessageRequest/RequestCatalogMessage.php index 08dafa8..622c412 100644 --- a/src/Request/MessageRequest/RequestCatalogMessage.php +++ b/src/Request/MessageRequest/RequestCatalogMessage.php @@ -11,17 +11,13 @@ final class RequestCatalogMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => 'interactive', - 'interactive' => [ - 'type' => $this->message->type(), - 'body' => ['text' => $this->message->body()], - 'action' => [ - 'name' => 'catalog_message', - ], + $body = parent::body(); + $body['type'] = 'interactive'; + $body['interactive'] = [ + 'type' => $this->message->type(), + 'body' => ['text' => $this->message->body()], + 'action' => [ + 'name' => 'catalog_message', ], ]; @@ -33,10 +29,6 @@ public function body(): array $body['interactive']['footer'] = ['text' => $this->message->footer()]; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestContactMessage.php b/src/Request/MessageRequest/RequestContactMessage.php index fb4d2eb..b4ee369 100644 --- a/src/Request/MessageRequest/RequestContactMessage.php +++ b/src/Request/MessageRequest/RequestContactMessage.php @@ -13,18 +13,14 @@ public function body(): array { $message_type = $this->message->type(); - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - $message_type => [ - [ - 'name' => [ - 'formatted_name' => $this->message->fullName(), - 'first_name' => $this->message->firstName(), - 'last_name' => $this->message->lastName(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body[$message_type] = [ + [ + 'name' => [ + 'formatted_name' => $this->message->fullName(), + 'first_name' => $this->message->firstName(), + 'last_name' => $this->message->lastName(), ], ], ]; @@ -42,10 +38,6 @@ public function body(): array $body[$message_type][0]['phones'][] = $phone_array; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestCtaUrlMessage.php b/src/Request/MessageRequest/RequestCtaUrlMessage.php index 7c1e8fb..05cefb3 100644 --- a/src/Request/MessageRequest/RequestCtaUrlMessage.php +++ b/src/Request/MessageRequest/RequestCtaUrlMessage.php @@ -11,15 +11,11 @@ final class RequestCtaUrlMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => 'interactive', - 'interactive' => [ - 'type' => $this->message->type(), - 'action' => $this->message->action(), - ], + $body = parent::body(); + $body['type'] = 'interactive'; + $body['interactive'] = [ + 'type' => $this->message->type(), + 'action' => $this->message->action(), ]; if ($this->message->header()) { @@ -34,10 +30,6 @@ public function body(): array $body['interactive']['footer'] = ['text' => $this->message->footer()]; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestDocumentMessage.php b/src/Request/MessageRequest/RequestDocumentMessage.php index a77c692..37484a6 100644 --- a/src/Request/MessageRequest/RequestDocumentMessage.php +++ b/src/Request/MessageRequest/RequestDocumentMessage.php @@ -11,22 +11,14 @@ final class RequestDocumentMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - 'document' => [ - 'caption' => $this->message->caption(), - 'filename' => $this->message->filename(), - $this->message->identifierType() => $this->message->identifierValue(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body['document'] = [ + 'caption' => $this->message->caption(), + 'filename' => $this->message->filename(), + $this->message->identifierType() => $this->message->identifierValue(), ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestImageMessage.php b/src/Request/MessageRequest/RequestImageMessage.php index 761a663..483c0ff 100644 --- a/src/Request/MessageRequest/RequestImageMessage.php +++ b/src/Request/MessageRequest/RequestImageMessage.php @@ -11,21 +11,13 @@ final class RequestImageMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - 'image' => [ - 'caption' => $this->message->caption(), - $this->message->identifierType() => $this->message->identifierValue(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body['image'] = [ + 'caption' => $this->message->caption(), + $this->message->identifierType() => $this->message->identifierValue(), ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestLocationMessage.php b/src/Request/MessageRequest/RequestLocationMessage.php index 926aa5c..0e770ef 100644 --- a/src/Request/MessageRequest/RequestLocationMessage.php +++ b/src/Request/MessageRequest/RequestLocationMessage.php @@ -11,23 +11,15 @@ final class RequestLocationMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - $this->message->type() => [ - 'longitude' => $this->message->longitude(), - 'latitude' => $this->message->latitude(), - 'name' => $this->message->name(), - 'address' => $this->message->address(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body[$this->message->type()] = [ + 'longitude' => $this->message->longitude(), + 'latitude' => $this->message->latitude(), + 'name' => $this->message->name(), + 'address' => $this->message->address(), ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestLocationRequestMessage.php b/src/Request/MessageRequest/RequestLocationRequestMessage.php index 4d4e2f5..2cc7558 100644 --- a/src/Request/MessageRequest/RequestLocationRequestMessage.php +++ b/src/Request/MessageRequest/RequestLocationRequestMessage.php @@ -11,24 +11,16 @@ final class RequestLocationRequestMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => 'interactive', - 'interactive' => [ - 'type' => $this->message->type(), - 'body' => ['text' => $this->message->body()], - 'action' => [ - 'name' => 'send_location', - ], + $body = parent::body(); + $body['type'] = 'interactive'; + $body['interactive'] = [ + 'type' => $this->message->type(), + 'body' => ['text' => $this->message->body()], + 'action' => [ + 'name' => 'send_location', ], ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestMultiProductMessage.php b/src/Request/MessageRequest/RequestMultiProductMessage.php index 6dad6b1..6a3b8f0 100644 --- a/src/Request/MessageRequest/RequestMultiProductMessage.php +++ b/src/Request/MessageRequest/RequestMultiProductMessage.php @@ -11,19 +11,15 @@ final class RequestMultiProductMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => 'interactive', - 'interactive' => [ - 'type' => $this->message->type(), - 'header' => $this->message->header(), - 'body' => ['text' => $this->message->body()], - 'action' => [ - 'catalog_id' => $this->message->catalog_id(), - 'sections' => $this->message->sections(), - ], + $body = parent::body(); + $body['type'] = 'interactive'; + $body['interactive'] = [ + 'type' => $this->message->type(), + 'header' => $this->message->header(), + 'body' => ['text' => $this->message->body()], + 'action' => [ + 'catalog_id' => $this->message->catalog_id(), + 'sections' => $this->message->sections(), ], ]; @@ -31,10 +27,6 @@ public function body(): array $body['interactive']['footer'] = ['text' => $this->message->footer()]; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestOptionsListMessage.php b/src/Request/MessageRequest/RequestOptionsListMessage.php index d758da2..fa7cf53 100644 --- a/src/Request/MessageRequest/RequestOptionsListMessage.php +++ b/src/Request/MessageRequest/RequestOptionsListMessage.php @@ -11,16 +11,12 @@ final class RequestOptionsListMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => 'interactive', - 'interactive' => [ - 'type' => $this->message->type(), - 'body' => ['text' => $this->message->body()], - 'action' => $this->message->action(), - ], + $body = parent::body(); + $body['type'] = 'interactive'; + $body['interactive'] = [ + 'type' => $this->message->type(), + 'body' => ['text' => $this->message->body()], + 'action' => $this->message->action(), ]; if ($this->message->header()) { @@ -34,10 +30,6 @@ public function body(): array $body['interactive']['footer'] = ['text' => $this->message->footer()]; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestReactionMessage.php b/src/Request/MessageRequest/RequestReactionMessage.php index 9bea82c..dd3956d 100644 --- a/src/Request/MessageRequest/RequestReactionMessage.php +++ b/src/Request/MessageRequest/RequestReactionMessage.php @@ -11,15 +11,11 @@ final class RequestReactionMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - $this->message->type() => [ - 'message_id' => $this->message->message_id(), - 'emoji' => $this->message->emoji(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body[$this->message->type()] = [ + 'message_id' => $this->message->message_id(), + 'emoji' => $this->message->emoji(), ]; return $body; diff --git a/src/Request/MessageRequest/RequestSingleProductMessage.php b/src/Request/MessageRequest/RequestSingleProductMessage.php index 80f691b..7b99a48 100644 --- a/src/Request/MessageRequest/RequestSingleProductMessage.php +++ b/src/Request/MessageRequest/RequestSingleProductMessage.php @@ -11,17 +11,13 @@ final class RequestSingleProductMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => 'interactive', - 'interactive' => [ - 'type' => $this->message->type(), - 'action' => [ - 'catalog_id' => $this->message->catalog_id(), - 'product_retailer_id' => $this->message->product_retailer_id(), - ], + $body = parent::body(); + $body['type'] = 'interactive'; + $body['interactive'] = [ + 'type' => $this->message->type(), + 'action' => [ + 'catalog_id' => $this->message->catalog_id(), + 'product_retailer_id' => $this->message->product_retailer_id(), ], ]; @@ -33,10 +29,6 @@ public function body(): array $body['interactive']['footer'] = ['text' => $this->message->footer()]; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestStickerMessage.php b/src/Request/MessageRequest/RequestStickerMessage.php index e746d4f..c2567c6 100644 --- a/src/Request/MessageRequest/RequestStickerMessage.php +++ b/src/Request/MessageRequest/RequestStickerMessage.php @@ -11,20 +11,12 @@ final class RequestStickerMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - $this->message->type() => [ - $this->message->identifierType() => $this->message->identifierValue(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body[$this->message->type()] = [ + $this->message->identifierType() => $this->message->identifierValue(), ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestTemplateMessage.php b/src/Request/MessageRequest/RequestTemplateMessage.php index 08a9f98..0e99251 100644 --- a/src/Request/MessageRequest/RequestTemplateMessage.php +++ b/src/Request/MessageRequest/RequestTemplateMessage.php @@ -11,16 +11,12 @@ final class RequestTemplateMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - 'template' => [ - 'name' => $this->message->name(), - 'language' => ['code' => $this->message->language()], - 'components' => [], - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body['template'] = [ + 'name' => $this->message->name(), + 'language' => ['code' => $this->message->language()], + 'components' => [], ]; if ($this->message->header()) { @@ -41,10 +37,6 @@ public function body(): array $body['template']['components'][] = $button; } - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestTextMessage.php b/src/Request/MessageRequest/RequestTextMessage.php index 00bbe3a..ac46b26 100644 --- a/src/Request/MessageRequest/RequestTextMessage.php +++ b/src/Request/MessageRequest/RequestTextMessage.php @@ -11,21 +11,13 @@ final class RequestTextMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - 'text' => [ - 'preview_url' => $this->message->previewUrl(), - 'body' => $this->message->text(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body['text'] = [ + 'preview_url' => $this->message->previewUrl(), + 'body' => $this->message->text(), ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/Request/MessageRequest/RequestVideoMessage.php b/src/Request/MessageRequest/RequestVideoMessage.php index cdc5322..fff842d 100644 --- a/src/Request/MessageRequest/RequestVideoMessage.php +++ b/src/Request/MessageRequest/RequestVideoMessage.php @@ -11,21 +11,13 @@ final class RequestVideoMessage extends MessageRequest */ public function body(): array { - $body = [ - 'messaging_product' => $this->message->messagingProduct(), - 'recipient_type' => $this->message->recipientType(), - 'to' => $this->message->to(), - 'type' => $this->message->type(), - $this->message->type() => [ - $this->message->identifierType() => $this->message->identifierValue(), - 'caption' => $this->message->caption(), - ], + $body = parent::body(); + $body['type'] = $this->message->type(); + $body[$this->message->type()] = [ + $this->message->identifierType() => $this->message->identifierValue(), + 'caption' => $this->message->caption(), ]; - if ($this->message->replyTo()) { - $body['context']['message_id'] = $this->message->replyTo(); - } - return $body; } } diff --git a/src/WebHook/Notification/MessageNotificationFactory.php b/src/WebHook/Notification/MessageNotificationFactory.php index 75a4632..38f0715 100644 --- a/src/WebHook/Notification/MessageNotificationFactory.php +++ b/src/WebHook/Notification/MessageNotificationFactory.php @@ -132,9 +132,12 @@ private function decorateNotification(MessageNotification $notification, array $ { if ($contact) { $notification->withCustomer(new Support\Customer( - $contact['wa_id'], + $contact['wa_id'] ?? null, $contact['profile']['name'] ?? '', - $message['from'] + $message['from'] ?? null, + $contact['profile']['username'] ?? null, + $contact['user_id'] ?? null, + $contact['parent_user_id'] ?? null )); } diff --git a/src/WebHook/Notification/Support/Customer.php b/src/WebHook/Notification/Support/Customer.php index 0ce44ef..b428a6c 100644 --- a/src/WebHook/Notification/Support/Customer.php +++ b/src/WebHook/Notification/Support/Customer.php @@ -6,15 +6,18 @@ final class Customer { private string $id; - private string $name; - - private string $phone_number; - - public function __construct(string $id, string $name, string $phone_number) - { - $this->id = $id; - $this->name = $name; - $this->phone_number = $phone_number; + private CustomerIdType $id_type; + + public function __construct( + ?string $wa_id, + private string $name, + private ?string $phone_number, + private ?string $username, + private ?string $user_id, + private ?string $parent_user_id + ) { + $this->id = $wa_id ?? $user_id ?? ''; + $this->id_type = null !== $wa_id ? new CustomerIdType(CustomerIdType::WA_ID) : (null !== $user_id ? new CustomerIdType(CustomerIdType::USER_ID) : new CustomerIdType(CustomerIdType::UNKNOWN)); } public function id(): string @@ -27,8 +30,28 @@ public function name(): string return $this->name; } - public function phoneNumber(): string + public function phoneNumber(): ?string { return $this->phone_number; } + + public function username(): ?string + { + return $this->username; + } + + public function userId(): ?string + { + return $this->user_id; + } + + public function parentUserId(): ?string + { + return $this->parent_user_id; + } + + public function idType(): string + { + return (string) $this->id_type; + } } diff --git a/src/WebHook/Notification/Support/CustomerIdType.php b/src/WebHook/Notification/Support/CustomerIdType.php new file mode 100644 index 0000000..78a6c32 --- /dev/null +++ b/src/WebHook/Notification/Support/CustomerIdType.php @@ -0,0 +1,20 @@ +type; + } +} diff --git a/src/WhatsAppCloudApi.php b/src/WhatsAppCloudApi.php index 5e93fa8..85a84cc 100644 --- a/src/WhatsAppCloudApi.php +++ b/src/WhatsAppCloudApi.php @@ -74,9 +74,9 @@ public function __construct(array $config) * * @throws Response\ResponseException */ - public function sendTextMessage(string $to, string $text, bool $preview_url = false): Response + public function sendTextMessage(?string $to, string $text, bool $preview_url = false, ?string $recipient = null): Response { - $message = new Message\TextMessage($to, $text, $preview_url, $this->reply_to); + $message = new Message\TextMessage($to, $text, $preview_url, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestTextMessage( $message, $this->app->accessToken(), @@ -91,15 +91,15 @@ public function sendTextMessage(string $to, string $text, bool $preview_url = fa * Sends a document uploaded to the WhatsApp Cloud servers by it Media ID or you also * can put any public URL of some document uploaded on Internet. * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. * @param MediaID $document_id WhatsApp Media ID or any Internet public document link. * @return Response * * @throws Response\ResponseException */ - public function sendDocument(string $to, MediaID $document_id, string $name, ?string $caption = ''): Response + public function sendDocument(?string $to, MediaID $document_id, string $name, ?string $caption = '', ?string $recipient = null): Response { - $message = new Message\DocumentMessage($to, $document_id, $name, $caption, $this->reply_to); + $message = new Message\DocumentMessage($to, $document_id, $name, $caption, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestDocumentMessage( $message, $this->app->accessToken(), @@ -113,7 +113,7 @@ public function sendDocument(string $to, MediaID $document_id, string $name, ?st /** * Sends a message template. * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. * @param string $template_name Name of the template to send. * @param string $language Language code * @param Component|null $component Component parameters of a template @@ -123,9 +123,9 @@ public function sendDocument(string $to, MediaID $document_id, string $name, ?st * * @throws Response\ResponseException */ - public function sendTemplate(string $to, string $template_name, string $language = 'en_US', ?Component $components = null): Response + public function sendTemplate(?string $to, string $template_name, string $language = 'en_US', ?Component $components = null, ?string $recipient = null): Response { - $message = new Message\TemplateMessage($to, $template_name, $language, $components, $this->reply_to); + $message = new Message\TemplateMessage($to, $template_name, $language, $components, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestTemplateMessage( $message, $this->app->accessToken(), @@ -140,15 +140,15 @@ public function sendTemplate(string $to, string $template_name, string $language * Sends an audio uploaded to the WhatsApp Cloud servers by it Media ID or you also * can put any public URL of some audio uploaded on Internet. * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. - * @param MediaId $audio_id WhatsApp Media ID or any Internet public audio link. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param MediaID $audio_id WhatsApp Media ID or any Internet public audio link. * @return Response * * @throws Response\ResponseException */ - public function sendAudio(string $to, MediaID $audio_id): Response + public function sendAudio(?string $to, MediaID $audio_id, ?string $recipient = null): Response { - $message = new Message\AudioMessage($to, $audio_id, $this->reply_to); + $message = new Message\AudioMessage($to, $audio_id, $recipient, $this->reply_to); $request = new Request\MessageRequest\RequestAudioMessage( $message, $this->app->accessToken(), @@ -163,16 +163,17 @@ public function sendAudio(string $to, MediaID $audio_id): Response * Sends an image uploaded to the WhatsApp Cloud servers by it Media ID or you also * can put any public URL of some image uploaded on Internet. * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. - * @param string $caption Description of the specified image file. - * @param MediaId $image_id WhatsApp Media ID or any Internet public image link. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $caption Description of the specified image file. + * @param MediaID $image_id WhatsApp Media ID or any Internet public image link. + * @param ?string $recipient Business-scoped user id (BSUID) recipient. * @return Response * * @throws Response\ResponseException */ - public function sendImage(string $to, MediaID $image_id, ?string $caption = ''): Response + public function sendImage(?string $to, MediaID $image_id, ?string $caption = '', ?string $recipient = null): Response { - $message = new Message\ImageMessage($to, $image_id, $caption, $this->reply_to); + $message = new Message\ImageMessage($to, $image_id, $caption, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestImageMessage( $message, $this->app->accessToken(), @@ -187,15 +188,15 @@ public function sendImage(string $to, MediaID $image_id, ?string $caption = ''): * Sends a video uploaded to the WhatsApp Cloud servers by it Media ID or you also * can put any public URL of some video uploaded on Internet. * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. - * @param MediaId $video_id WhatsApp Media ID or any Internet public video link. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param MediaID $video_id WhatsApp Media ID or any Internet public video link. * @return Response * * @throws Response\ResponseException */ - public function sendVideo(string $to, MediaID $video_id, string $caption = ''): Response + public function sendVideo(?string $to, MediaID $video_id, string $caption = '', ?string $recipient = null): Response { - $message = new Message\VideoMessage($to, $video_id, $caption, $this->reply_to); + $message = new Message\VideoMessage($to, $video_id, $caption, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestVideoMessage( $message, $this->app->accessToken(), @@ -210,15 +211,15 @@ public function sendVideo(string $to, MediaID $video_id, string $caption = ''): * Sends a sticker uploaded to the WhatsApp Cloud servers by it Media ID or you also * can put any public URL of some sticker uploaded on Internet. * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. - * @param MediaId $sticker_id WhatsApp Media ID or any Internet public sticker link. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param MediaID $sticker_id WhatsApp Media ID or any Internet public sticker link. * @return Response * * @throws Response\ResponseException */ - public function sendSticker(string $to, MediaID $sticker_id): Response + public function sendSticker(?string $to, MediaID $sticker_id, ?string $recipient = null): Response { - $message = new Message\StickerMessage($to, $sticker_id, $this->reply_to); + $message = new Message\StickerMessage($to, $sticker_id, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestStickerMessage( $message, $this->app->accessToken(), @@ -232,19 +233,19 @@ public function sendSticker(string $to, MediaID $sticker_id): Response /** * Sends a location * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. * @param float $longitude Longitude position. * @param float $latitude Latitude position. * @param string $name Name of location sent. - * @param address $address Address of location sent. + * @param string $address Address of location sent. * * @return Response * * @throws Response\ResponseException */ - public function sendLocation(string $to, float $longitude, float $latitude, string $name = '', string $address = ''): Response + public function sendLocation(?string $to, float $longitude, float $latitude, string $name = '', string $address = '', ?string $recipient = null): Response { - $message = new Message\LocationMessage($to, $longitude, $latitude, $name, $address, $this->reply_to); + $message = new Message\LocationMessage($to, $longitude, $latitude, $name, $address, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestLocationMessage( $message, $this->app->accessToken(), @@ -258,16 +259,16 @@ public function sendLocation(string $to, float $longitude, float $latitude, stri /** * Sends a location request message. * - * @param string $to The WhatsApp ID or phone number for the person you want to send the message to. + * @param ?string $to The WhatsApp ID or phone number for the person you want to send the message to. * @param string $body The body of the location request message. * * @return Response The response object containing the result of the API request. * * @throws Response\ResponseException If there's an error with the API request. */ - public function sendLocationRequest(string $to, string $body) + public function sendLocationRequest(?string $to, string $body, ?string $recipient = null) { - $message = new Message\LocationRequestMessage($to, $body, $this->reply_to); + $message = new Message\LocationRequestMessage($to, $body, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestLocationRequestMessage( $message, $this->app->accessToken(), @@ -281,17 +282,18 @@ public function sendLocationRequest(string $to, string $body) /** * Sends a contact * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. - * @param ContactName $name The contact name object. - * @param Phone|null $phone The contact phone number. + * @param string|null $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ContactName $name The contact name object. + * @param string|null $recipient Business-scoped user id (BSUID) recipient. + * @param Phone ...$phone The contact phone numbers from the user's address book. * * @return Response * * @throws Response\ResponseException */ - public function sendContact(string $to, ContactName $name, Phone ...$phone): Response + public function sendContact(?string $to, ContactName $name, ?string $recipient = null, Phone ...$phone): Response { - $message = new Message\ContactMessage($to, $name, $this->reply_to, ...$phone); + $message = new Message\ContactMessage($to, $name, $this->reply_to, $recipient, ...$phone); $request = new Request\MessageRequest\RequestContactMessage( $message, $this->app->accessToken(), @@ -305,7 +307,7 @@ public function sendContact(string $to, ContactName $name, Phone ...$phone): Res /** * Sends a list * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. * @param string $header The header. * @param string $body The body. * @param string $footer The footer. @@ -315,9 +317,9 @@ public function sendContact(string $to, ContactName $name, Phone ...$phone): Res * * @throws Response\ResponseException */ - public function sendList(string $to, string $header, string $body, string $footer, Action $action): Response + public function sendList(?string $to, string $header, string $body, string $footer, Action $action, ?string $recipient = null): Response { - $message = new Message\OptionsListMessage($to, $header, $body, $footer, $action, $this->reply_to); + $message = new Message\OptionsListMessage($to, $header, $body, $footer, $action, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestOptionsListMessage( $message, $this->app->accessToken(), @@ -331,7 +333,7 @@ public function sendList(string $to, string $header, string $body, string $foote /** * Sends a CTA URL * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. * @param string $displayText The display text. * @param string $url The URL. * @param ?Header $header The header. @@ -342,9 +344,9 @@ public function sendList(string $to, string $header, string $body, string $foote * * @throws Response\ResponseException */ - public function sendCtaUrl(string $to, string $displayText, string $url, ?Header $header, ?string $body, ?string $footer): Response + public function sendCtaUrl(?string $to, string $displayText, string $url, ?Header $header, ?string $body, ?string $footer, ?string $recipient = null): Response { - $message = new Message\CtaUrlMessage($to, $displayText, $url, $header, $body, $footer, $this->reply_to); + $message = new Message\CtaUrlMessage($to, $displayText, $url, $header, $body, $footer, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestCtaUrlMessage( $message, $this->app->accessToken(), @@ -355,7 +357,7 @@ public function sendCtaUrl(string $to, string $displayText, string $url, ?Header return $this->client->sendMessage($request); } - public function sendButton(string $to, string $body, ButtonAction $action, ?string $header = null, ?string $footer = null): Response + public function sendButton(?string $to, string $body, ButtonAction $action, ?string $header = null, ?string $footer = null, ?string $recipient = null): Response { $message = new Message\ButtonReplyMessage( $to, @@ -363,7 +365,8 @@ public function sendButton(string $to, string $body, ButtonAction $action, ?stri $action, $header, $footer, - $this->reply_to + $this->reply_to, + $recipient ); $request = new Request\MessageRequest\RequestButtonReplyMessage( @@ -400,7 +403,7 @@ public function uploadMedia(string $file_path): Response /** * Sends a message with multiple products to a user. * - * @param string $to The WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $to The WhatsApp ID or phone number for the person you want to send a message to. * @param int $catalog_id The ID of the catalog where the products are located. * @param MultiProductAction $action The contents of the catalog products to be sent. * @param string $header The header of the message. @@ -411,9 +414,9 @@ public function uploadMedia(string $file_path): Response * * @throws Response\ResponseException If the API request fails. */ - public function sendMultiProduct(string $to, int $catalog_id, MultiProductAction $action, string $header, string $body, ?string $footer = '') + public function sendMultiProduct(?string $to, int $catalog_id, MultiProductAction $action, string $header, string $body, ?string $footer = '', ?string $recipient = null) { - $message = new Message\MultiProductMessage($to, $catalog_id, $action, $header, $body, $footer, $this->reply_to); + $message = new Message\MultiProductMessage($to, $catalog_id, $action, $header, $body, $footer, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestMultiProductMessage( $message, $this->app->accessToken(), @@ -447,7 +450,7 @@ public function downloadMedia(string $media_id): Response /** * Sends a catalog message. * - * @param string $to WhatsApp ID or phone number for the person you want to send the message to. + * @param ?string $to WhatsApp ID or phone number for the person you want to send the message to. * @param string $body The body of the catalog message. * @param ?string $footer The footer of the catalog message. * @param ?string $thumbnail_product_retailer_id The product retailer ID to use as thumbnail. @@ -455,9 +458,9 @@ public function downloadMedia(string $media_id): Response * * @throws Response\ResponseException */ - public function sendCatalog(string $to, string $body, ?string $footer = '', ?string $thumbnail_product_retailer_id = '') + public function sendCatalog(?string $to, string $body, ?string $footer = '', ?string $thumbnail_product_retailer_id = '', ?string $recipient = null) { - $message = new Message\CatalogMessage($to, $body, $footer, $thumbnail_product_retailer_id, $this->reply_to); + $message = new Message\CatalogMessage($to, $body, $footer, $thumbnail_product_retailer_id, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestCatalogMessage( $message, $this->app->accessToken(), @@ -471,7 +474,7 @@ public function sendCatalog(string $to, string $body, ?string $footer = '', ?str /** * Mark a message as read * - * @param string $message_id WhatsApp Message Id will be marked as read. + * @param string $message_id Identifier of the WhatsApp message to mark as read. * * @return Response * @@ -496,7 +499,7 @@ public function markMessageAsRead(string $message_id): Response * or until a message is sent, whichever comes first. * Also marks the referenced message as read. * - * @param string $message_id WhatsApp Message Id to show typing indicator for. + * @param string $message_id Identifier of the WhatsApp message to show a typing indicator for. * * @return Response * @@ -517,16 +520,16 @@ public function sendTypingIndicator(string $message_id): Response /** * Sends a reaction to a provided message id. * - * @param string $to WhatsApp ID or phone number for the person you want to send a message to. + * @param ?string $to WhatsApp ID or phone number for the person you want to send a message to. * @param string $message_id The ID of the message to react to. * @param string $emoji The emoji to use as a reaction. * @return Response * * @throws Response\ResponseException */ - public function sendReaction(string $to, string $message_id, string $emoji = ''): Response + public function sendReaction(?string $to, string $message_id, string $emoji = '', ?string $recipient = null): Response { - $message = new Message\ReactionMessage($to, $message_id, $emoji); + $message = new Message\ReactionMessage($to, $message_id, $emoji, $recipient); $request = new Request\MessageRequest\RequestReactionMessage( $message, @@ -541,7 +544,7 @@ public function sendReaction(string $to, string $message_id, string $emoji = '') /** * Sends a single product message to a specified recipient. * - * @param string $to The WhatsApp ID or phone number for the person you want to send the message to. + * @param ?string $to The WhatsApp ID or phone number for the person you want to send the message to. * @param int $catalog_id The ID of the catalog where the product is located. * @param string $product_retailer_id The retailer-specific ID of the product. * @param string|null $body The body of the message. Defaults to an empty string if not provided. @@ -551,9 +554,9 @@ public function sendReaction(string $to, string $message_id, string $emoji = '') * * @throws Response\ResponseException If there is an error with the API request. */ - public function sendSingleProduct(string $to, int $catalog_id, string $product_retailer_id, ?string $body = '', ?string $footer = '') + public function sendSingleProduct(?string $to, int $catalog_id, string $product_retailer_id, ?string $body = '', ?string $footer = '', ?string $recipient = null) { - $message = new Message\SingleProductMessage($to, $catalog_id, $product_retailer_id, $body, $footer, $this->reply_to); + $message = new Message\SingleProductMessage($to, $catalog_id, $product_retailer_id, $body, $footer, $this->reply_to, $recipient); $request = new Request\MessageRequest\RequestSingleProductMessage( $message, $this->app->accessToken(), diff --git a/tests/Integration/WhatsAppCloudApiTest.php b/tests/Integration/WhatsAppCloudApiTest.php index 0a24d0d..fc53e73 100644 --- a/tests/Integration/WhatsAppCloudApiTest.php +++ b/tests/Integration/WhatsAppCloudApiTest.php @@ -231,6 +231,7 @@ public function test_send_contact() $response = $this->whatsapp_app_cloud_api->sendContact( WhatsAppCloudApiTestConfiguration::$to_phone_number_id, $contact_name, + null, $phone ); @@ -245,6 +246,7 @@ public function test_send_contact_with_waid() $response = $this->whatsapp_app_cloud_api->sendContact( WhatsAppCloudApiTestConfiguration::$to_phone_number_id, $contact_name, + null, $phone ); diff --git a/tests/Unit/WebHook/NotificationFactoryTest.php b/tests/Unit/WebHook/NotificationFactoryTest.php index 32701c4..2833049 100644 --- a/tests/Unit/WebHook/NotificationFactoryTest.php +++ b/tests/Unit/WebHook/NotificationFactoryTest.php @@ -1489,4 +1489,89 @@ public function test_build_from_payload_without_contact_profile_can_build_a_noti $this->assertInstanceOf(Notification\Text::class, $notification); $this->assertEquals('MESSAGE_BODY', $notification->message()); } + + public function test_build_from_payload_can_build_a_notification_with_bsuid_only_and_no_from() + { + $payload = json_decode('{ + "object": "whatsapp_business_account", + "entry": [{ + "id": "WHATSAPP_BUSINESS_ACCOUNT_ID", + "changes": [{ + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "PHONE_NUMBER", + "phone_number_id": "PHONE_NUMBER_ID" + }, + "contacts": [{ + "profile": { + "name": "NAME" + }, + "user_id": "BSUID_123" + }], + "messages": [{ + "id": "wamid.ID", + "timestamp": "1669233778", + "text": { + "body": "MESSAGE_BODY" + }, + "type": "text" + }] + }, + "field": "messages" + }] + }] + }', true); + + $notification = $this->notification_factory->buildFromPayload($payload); + + $this->assertInstanceOf(Notification\Text::class, $notification); + $this->assertEquals('BSUID_123', $notification->customer()->id()); + $this->assertEquals('BSUID_123', $notification->customer()->userId()); + $this->assertEquals('user_id', $notification->customer()->idType()); + $this->assertNull($notification->customer()->phoneNumber()); + } + + public function test_build_from_payload_can_keep_wa_id_priority_when_user_id_is_present() + { + $payload = json_decode('{ + "object": "whatsapp_business_account", + "entry": [{ + "id": "WHATSAPP_BUSINESS_ACCOUNT_ID", + "changes": [{ + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "PHONE_NUMBER", + "phone_number_id": "PHONE_NUMBER_ID" + }, + "contacts": [{ + "profile": { + "name": "NAME" + }, + "wa_id": "WHATSAPP_ID", + "user_id": "BSUID_123" + }], + "messages": [{ + "from": "PHONE_NUMBER", + "id": "wamid.ID", + "timestamp": "1669233778", + "text": { + "body": "MESSAGE_BODY" + }, + "type": "text" + }] + }, + "field": "messages" + }] + }] + }', true); + + $notification = $this->notification_factory->buildFromPayload($payload); + + $this->assertInstanceOf(Notification\Text::class, $notification); + $this->assertEquals('WHATSAPP_ID', $notification->customer()->id()); + $this->assertEquals('BSUID_123', $notification->customer()->userId()); + $this->assertEquals('wa_id', $notification->customer()->idType()); + } } diff --git a/tests/Unit/WhatsAppCloudApiTest.php b/tests/Unit/WhatsAppCloudApiTest.php index 1754957..1d37fa1 100644 --- a/tests/Unit/WhatsAppCloudApiTest.php +++ b/tests/Unit/WhatsAppCloudApiTest.php @@ -12,6 +12,7 @@ use Netflie\WhatsAppCloudApi\Message\Contact\Phone; use Netflie\WhatsAppCloudApi\Message\Contact\PhoneType; use Netflie\WhatsAppCloudApi\Message\CtaUrl\TitleHeader; +use Netflie\WhatsAppCloudApi\Message\Error\InvalidMessage; use Netflie\WhatsAppCloudApi\Message\Media\LinkID; use Netflie\WhatsAppCloudApi\Message\Media\MediaObjectID; use Netflie\WhatsAppCloudApi\Message\MultiProduct\Action as MultiProductAction; @@ -810,6 +811,7 @@ public function test_send_contact() ->sendContact( $to, $contact_name, + null, new Phone($phone, $phone_type) ); @@ -863,6 +865,7 @@ public function test_send_contact_with_wa_id() $response = $this->whatsapp_app_cloud_api->sendContact( $to, $contact_name, + null, new Phone($phone, $phone_type, $phone) ); @@ -872,6 +875,59 @@ public function test_send_contact_with_wa_id() $this->assertEquals(false, $response->isError()); } + public function test_send_contact_with_recipient_only() + { + $recipient = 'US.13491208655302741918'; + $url = $this->buildMessageRequestUri(); + $first_name = $this->faker->firstName(); + $last_name = $this->faker->lastName; + $phone = $this->faker->e164PhoneNumber; + $phone_type = PhoneType::CELL(); + + $body = [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'recipient' => $recipient, + 'type' => 'contacts', + 'contacts' => [ + [ + 'name' => [ + 'formatted_name' => "$first_name $last_name", + 'first_name' => $first_name, + 'last_name' => $last_name, + ], + 'phones' => [ + [ + 'phone' => $phone, + 'type' => $phone_type, + ], + ], + ], + ], + ]; + $headers = [ + 'Authorization' => 'Bearer ' . $this->access_token, + ]; + + $this->client_handler + ->postJsonData($url, $body, $headers, Argument::type('int')) + ->shouldBeCalled() + ->willReturn(new RawResponse($headers, $this->successfulMessageNodeResponse(), 200)); + + $contact_name = new ContactName($first_name, $last_name); + $response = $this->whatsapp_app_cloud_api->sendContact( + null, + $contact_name, + $recipient, + new Phone($phone, $phone_type) + ); + + $this->assertEquals(200, $response->httpStatusCode()); + $this->assertEquals(json_decode($this->successfulMessageNodeResponse(), true), $response->decodedBody()); + $this->assertEquals($this->successfulMessageNodeResponse(), $response->body()); + $this->assertEquals(false, $response->isError()); + } + public function test_send_list() { $to = $this->faker->phoneNumber; @@ -1022,8 +1078,7 @@ public function test_send_reply_buttons() 'reply' => $button, ]; } - - $message = $this->faker->text(50); + $message = $this->faker->text(1024); $header = $this->faker->text(50); $footer = $this->faker->text(50); @@ -1484,6 +1539,242 @@ public function test_send_remove_reaction_message() $this->assertEquals(false, $response->isError()); } + public function test_send_text_message_with_recipient_only() + { + $recipient = 'US.13491208655302741918'; + $url = $this->buildMessageRequestUri(); + $text_message = $this->faker->text; + $preview_url = $this->faker->boolean; + + $body = [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'recipient' => $recipient, + 'type' => 'text', + 'text' => [ + 'preview_url' => $preview_url, + 'body' => $text_message, + ], + ]; + $headers = [ + 'Authorization' => 'Bearer ' . $this->access_token, + ]; + + $this->client_handler + ->postJsonData($url, $body, $headers, Argument::type('int')) + ->shouldBeCalled() + ->willReturn(new RawResponse($headers, $this->successfulMessageNodeResponse(), 200)); + + $response = $this->whatsapp_app_cloud_api->sendTextMessage( + null, + $text_message, + $preview_url, + $recipient + ); + + $this->assertEquals(200, $response->httpStatusCode()); + $this->assertEquals(json_decode($this->successfulMessageNodeResponse(), true), $response->decodedBody()); + $this->assertEquals($this->successfulMessageNodeResponse(), $response->body()); + $this->assertEquals(false, $response->isError()); + } + + public function test_send_text_message_requires_to_or_recipient() + { + $this->expectException(InvalidMessage::class); + $this->expectExceptionMessage('Either `to` or `recipient` is required.'); + + $this->whatsapp_app_cloud_api->sendTextMessage(null, 'Hello world'); + } + + public function test_send_text_message_with_to_and_recipient() + { + $to = $this->faker->phoneNumber; + $recipient = 'US.13491208655302741918'; + $url = $this->buildMessageRequestUri(); + $text_message = $this->faker->text; + $preview_url = $this->faker->boolean; + + $body = [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'to' => $to, + 'recipient' => $recipient, + 'type' => 'text', + 'text' => [ + 'preview_url' => $preview_url, + 'body' => $text_message, + ], + ]; + $headers = [ + 'Authorization' => 'Bearer ' . $this->access_token, + ]; + + $this->client_handler + ->postJsonData($url, $body, $headers, Argument::type('int')) + ->shouldBeCalled() + ->willReturn(new RawResponse($headers, $this->successfulMessageNodeResponse(), 200)); + + $response = $this->whatsapp_app_cloud_api->sendTextMessage( + $to, + $text_message, + $preview_url, + $recipient + ); + + $this->assertEquals(200, $response->httpStatusCode()); + $this->assertEquals(json_decode($this->successfulMessageNodeResponse(), true), $response->decodedBody()); + $this->assertEquals($this->successfulMessageNodeResponse(), $response->body()); + $this->assertEquals(false, $response->isError()); + } + + public function test_send_template_with_recipient_only() + { + $recipient = 'US.13491208655302741918'; + $url = $this->buildMessageRequestUri(); + $template_name = $this->faker->text(10); + $language = 'en_US'; + + $body = [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'recipient' => $recipient, + 'type' => 'template', + 'template' => [ + 'name' => $template_name, + 'language' => ['code' => $language], + 'components' => [], + ], + ]; + $headers = [ + 'Authorization' => 'Bearer ' . $this->access_token, + ]; + + $this->client_handler + ->postJsonData($url, $body, $headers, Argument::type('int')) + ->shouldBeCalled() + ->willReturn(new RawResponse($headers, $this->successfulMessageNodeResponse(), 200)); + + $response = $this->whatsapp_app_cloud_api->sendTemplate( + null, + $template_name, + $language, + null, + $recipient + ); + + $this->assertEquals(200, $response->httpStatusCode()); + $this->assertEquals(json_decode($this->successfulMessageNodeResponse(), true), $response->decodedBody()); + $this->assertEquals($this->successfulMessageNodeResponse(), $response->body()); + $this->assertEquals(false, $response->isError()); + } + + public function test_send_list_with_recipient_only() + { + $recipient = 'US.13491208655302741918'; + $url = $this->buildMessageRequestUri(); + + $listHeader = ['type' => 'text', 'text' => $this->faker->text(60)]; + $listBody = ['text' => $this->faker->text(1024)]; + $listFooter = ['text' => $this->faker->text(60)]; + + $listRows = [ + ['id' => $this->faker->uuid, 'title' => $this->faker->text(24), 'description' => $this->faker->text(72)], + ]; + $listSections = [['title' => $this->faker->text, 'rows' => $listRows]]; + $listAction = ['button' => $this->faker->text, 'sections' => $listSections]; + + $body = [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'recipient' => $recipient, + 'type' => 'interactive', + 'interactive' => [ + 'type' => 'list', + 'header' => $listHeader, + 'body' => $listBody, + 'footer' => $listFooter, + 'action' => $listAction, + ], + ]; + $headers = [ + 'Authorization' => 'Bearer ' . $this->access_token, + ]; + + $this->client_handler + ->postJsonData($url, $body, $headers, Argument::type('int')) + ->shouldBeCalled() + ->willReturn(new RawResponse($headers, $this->successfulMessageNodeResponse(), 200)); + + $actionSections = []; + + foreach ($listAction['sections'] as $section) { + $sectionRows = []; + + foreach ($section['rows'] as $row) { + $sectionRows[] = new Row($row['id'], $row['title'], $row['description']); + } + + $actionSections[] = new Section($section['title'], $sectionRows); + } + + $response = $this->whatsapp_app_cloud_api->sendList( + null, + $listHeader['text'], + $listBody['text'], + $listFooter['text'], + new Action($listAction['button'], $actionSections), + $recipient + ); + + $this->assertEquals(200, $response->httpStatusCode()); + $this->assertEquals(json_decode($this->successfulMessageNodeResponse(), true), $response->decodedBody()); + $this->assertEquals($this->successfulMessageNodeResponse(), $response->body()); + $this->assertEquals(false, $response->isError()); + } + + public function test_send_document_with_recipient_only() + { + $recipient = 'US.13491208655302741918'; + $url = $this->buildMessageRequestUri(); + $caption = $this->faker->text; + $filename = $this->faker->text; + $document_id = $this->faker->uuid; + + $body = [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'recipient' => $recipient, + 'type' => 'document', + 'document' => [ + 'caption' => $caption, + 'filename' => $filename, + 'id' => $document_id, + ], + ]; + $headers = [ + 'Authorization' => 'Bearer ' . $this->access_token, + ]; + + $this->client_handler + ->postJsonData($url, $body, $headers, Argument::type('int')) + ->shouldBeCalled() + ->willReturn(new RawResponse($headers, $this->successfulMessageNodeResponse(), 200)); + + $media_id = new MediaObjectID($document_id); + $response = $this->whatsapp_app_cloud_api->sendDocument( + null, + $media_id, + $filename, + $caption, + $recipient + ); + + $this->assertEquals(200, $response->httpStatusCode()); + $this->assertEquals(json_decode($this->successfulMessageNodeResponse(), true), $response->decodedBody()); + $this->assertEquals($this->successfulMessageNodeResponse(), $response->body()); + $this->assertEquals(false, $response->isError()); + } + private function buildBaseUri(): string { return Client::BASE_GRAPH_URL . '/' . static::TEST_GRAPH_VERSION . '/';