From f60a4c4de14647377de9d309c52b3e36acd6087c Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Thu, 14 May 2026 07:18:57 +0530 Subject: [PATCH 1/2] feat: add AfricasTalking SMS adapter - New SMS adapter for AfricasTalking API - Supports sending SMS messages with optional 'from' field - Handles up to 100 messages per request - Proper error handling for API responses - Includes integration tests --- .../Messaging/Adapter/SMS/AfricasTalking.php | 75 +++++++++++++++++++ .../Adapter/SMS/AfricasTalkingTest.php | 60 +++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php create mode 100644 tests/Messaging/Adapter/SMS/AfricasTalkingTest.php diff --git a/src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php b/src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php new file mode 100644 index 00000000..e4abbe41 --- /dev/null +++ b/src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php @@ -0,0 +1,75 @@ +getType()); + + $recipients = \array_map( + fn ($to) => \ltrim($to, '+'), + $message->getTo() + ); + + $from = $this->from ?? $message->getFrom(); + + foreach ($recipients as $recipient) { + $result = $this->request( + method: 'POST', + url: self::API_URL, + headers: [ + 'Content-Type: application/x-www-form-urlencoded', + 'apiKey: ' . $this->apiKey, + ], + body: [ + 'username' => $this->username, + 'to' => $recipient, + 'message' => $message->getContent(), + 'from' => $from, + ] + ); + + if ($result['statusCode'] === 201) { + $response->incrementDeliveredTo(); + $response->addResult($recipient); + } else { + $errorMessage = 'Unknown error'; + if (isset($result['response']['errorMessage'])) { + $errorMessage = $result['response']['errorMessage']; + } elseif (isset($result['response']['message'])) { + $errorMessage = $result['response']['message']; + } + $response->addResult($recipient, $errorMessage); + } + } + + return $response->toArray(); + } +} \ No newline at end of file diff --git a/tests/Messaging/Adapter/SMS/AfricasTalkingTest.php b/tests/Messaging/Adapter/SMS/AfricasTalkingTest.php new file mode 100644 index 00000000..3d7805e6 --- /dev/null +++ b/tests/Messaging/Adapter/SMS/AfricasTalkingTest.php @@ -0,0 +1,60 @@ +markTestSkipped('AfricasTalking credentials are not available.'); + } + + $sender = new AfricasTalking($username, $apiKey); + + $message = new SMS( + to: [$to], + content: 'Test Content', + from: \getenv('AFRICAS_TALKING_FROM') ?: null + ); + + $response = $sender->send($message); + + $result = \json_decode($response, true); + + $this->assertResponse($result); + } + + public function testSendSMSWithFrom(): void + { + $username = \getenv('AFRICAS_TALKING_USERNAME'); + $apiKey = \getenv('AFRICAS_TALKING_API_KEY'); + $to = \getenv('AFRICAS_TALKING_TO'); + $from = \getenv('AFRICAS_TALKING_FROM'); + + if (empty($username) || empty($apiKey) || empty($to)) { + $this->markTestSkipped('AfricasTalking credentials are not available.'); + } + + $sender = new AfricasTalking($username, $apiKey, $from ?: 'AFRICAST'); + + $message = new SMS( + to: [$to], + content: 'Test Content with custom from' + ); + + $response = $sender->send($message); + + $result = \json_decode($response, true); + + $this->assertResponse($result); + } +} \ No newline at end of file From eaec94e0b240465f71d20b6cd012d0141e0a764d Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Fri, 19 Jun 2026 08:06:28 +0530 Subject: [PATCH 2/2] fix: add parent constructor, type-safe error handling, fix test json_decode to assertResponse --- src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php | 9 +++++++-- tests/Messaging/Adapter/SMS/AfricasTalkingTest.php | 8 ++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php b/src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php index e4abbe41..cbbea7eb 100644 --- a/src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php +++ b/src/Utopia/Messaging/Adapter/SMS/AfricasTalking.php @@ -17,6 +17,7 @@ public function __construct( private string $apiKey, private ?string $from = null ) { + parent::__construct(); } public function getName(): string @@ -62,9 +63,13 @@ protected function process(SMSMessage $message): array } else { $errorMessage = 'Unknown error'; if (isset($result['response']['errorMessage'])) { - $errorMessage = $result['response']['errorMessage']; + $errorMessage = \is_string($result['response']['errorMessage']) + ? $result['response']['errorMessage'] + : \json_encode($result['response']['errorMessage']); } elseif (isset($result['response']['message'])) { - $errorMessage = $result['response']['message']; + $errorMessage = \is_string($result['response']['message']) + ? $result['response']['message'] + : \json_encode($result['response']['message']); } $response->addResult($recipient, $errorMessage); } diff --git a/tests/Messaging/Adapter/SMS/AfricasTalkingTest.php b/tests/Messaging/Adapter/SMS/AfricasTalkingTest.php index 3d7805e6..98edcdee 100644 --- a/tests/Messaging/Adapter/SMS/AfricasTalkingTest.php +++ b/tests/Messaging/Adapter/SMS/AfricasTalkingTest.php @@ -28,9 +28,7 @@ public function testSendSMS(): void $response = $sender->send($message); - $result = \json_decode($response, true); - - $this->assertResponse($result); + $this->assertResponse($response); } public function testSendSMSWithFrom(): void @@ -53,8 +51,6 @@ public function testSendSMSWithFrom(): void $response = $sender->send($message); - $result = \json_decode($response, true); - - $this->assertResponse($result); + $this->assertResponse($response); } } \ No newline at end of file