Skip to content

Commit

Permalink
Merge pull request #21 from marventhieme/feature/encoding-detection
Browse files Browse the repository at this point in the history
Encoding detection, support for non-latin characters in SMS
  • Loading branch information
mbardelmeijer authored Jan 29, 2025
2 parents 2deeb96 + 7dfccb6 commit 93e1dc1
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 82 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea/
/vendor
build
composer.phar
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes to `CMSMS` will be documented in this file

## [4.0.0] - 2025-02-??
#### Changed
- Moved from XML to JSON for the request body
- Changed CM endpoint
#### Added
- Added config value for encoding detection type
- Two events for success and failure: `SMSSentSuccessfullyEvent` and `SMSSendingFailedEvent`
#### Removed
- Removed `tariff` support

## [3.3.0] - 2024-03-22
#### Added
- Laravel 11 support

## [3.3.0] - 2024-03-22
#### Changed
- Update CM endpoint by @marventhieme

## [3.2.0] - 2023-03-29
#### Changed
- Added support for Laravel 10.0 (#17) by @charleskoko
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,14 @@ public function routeNotificationForCmsms()
- `body('')`: Accepts a string value for the message body.
- `originator('')`: Accepts a string value between 1 and 11 characters, used as the message sender name.
- `reference('')`: Accepts a string value for your message reference. This information will be returned in a status report so you can match the message and it's status. Restrictions: 1 - 32 alphanumeric characters. Reference will not work for demo accounts.
- `tariff()`: Accepts a integer value for your message tariff. The unit is eurocent. Requires the `originator` to be set to a specific value. Contact CM for this tariff value. CM also must enable this feature for your contract manually.
- `encodingDetectionType('')`: Read about encoding detection here: https://developers.cm.com/messaging/docs/sms#auto-detect-encoding
- `multipart($minimum, $maximum)`: Accepts a 0 to 8 integer range which allows multipart messages. See the [documentation from CM](https://dashboard.onlinesmsgateway.com/docs#send-a-message-multipart) for more information.

### Available events
- `SMSSentSuccessfullyEvent`: This event will be fired after the message was sent. The event will contain the payload we have sent to CM.
- `SMSSendingFailedEvent`: This event will be fired if the message was not sent. The event will contain the response body we received from CM.


## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
Expand Down
84 changes: 64 additions & 20 deletions src/CmsmsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,102 @@
namespace NotificationChannels\Cmsms;

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Arr;
use NotificationChannels\Cmsms\Events\SMSSendingFailedEvent;
use NotificationChannels\Cmsms\Events\SMSSentSuccessfullyEvent;
use NotificationChannels\Cmsms\Exceptions\CouldNotSendNotification;
use SimpleXMLElement;
use NotificationChannels\Cmsms\Exceptions\InvalidMessage;

class CmsmsClient
{
public const GATEWAY_URL = 'https://gw.messaging.cm.com/gateway.ashx';
public const GATEWAY_URL = 'https://gw.cmtelecom.com/v1.0/message';

public function __construct(
protected GuzzleClient $client,
protected string $productToken,
) {
}

/**
* @throws InvalidMessage
* @throws GuzzleException
* @throws CouldNotSendNotification
*/
public function send(CmsmsMessage $message, string $recipient): void
{
if (is_null(Arr::get($message->toXmlArray(), 'FROM'))) {
if (empty($message->getOriginator())) {
$message->originator(config('services.cmsms.originator'));
}

$payload = $this->buildMessageJson($message, $recipient);

$response = $this->client->request('POST', static::GATEWAY_URL, [
'body' => $this->buildMessageXml($message, $recipient),
'body' => $payload,
'headers' => [
'Content-Type' => 'application/xml',
'accept' => 'application/json',
'content-type' => 'application/json',
],
]);

// API returns an empty string on success
// On failure, only the error string is passed
/**
* If error code is 0, the message was sent successfully.
*/
$body = $response->getBody()->getContents();
if (! empty($body)) {
$errorCode = Arr::get(json_decode($body, true), 'errorCode');
if ((int) $errorCode !== 0) {
SMSSendingFailedEvent::dispatch($body);

throw CouldNotSendNotification::serviceRespondedWithAnError($body);
}

SMSSentSuccessfullyEvent::dispatch($payload);
}

public function buildMessageXml(CmsmsMessage $message, string $recipient): string
/**
* See: https://developers.cm.com/messaging/reference/messages_sendmessage-1
*/
public function buildMessageJson(CmsmsMessage $message, string $recipient): string
{
$xml = new SimpleXMLElement('<MESSAGES/>');

$xml->addChild('AUTHENTICATION')
->addChild('PRODUCTTOKEN', $this->productToken);
$body = [];
$body['content'] = $message->getBody();
if (strtoupper($message->getEncodingDetectionType()) === 'AUTO') {
$body['type'] = 'AUTO';
}

if ($tariff = $message->getTariff()) {
$xml->addChild('TARIFF', (string) $tariff);
$minimumNumberOfMessageParts = [];
if ($message->getMinimumNumberOfMessageParts() !== null) {
$minimumNumberOfMessageParts['minimumNumberOfMessageParts'] = $message->getMinimumNumberOfMessageParts();
}
$maximumNumberOfMessageParts = [];
if ($message->getMaximumNumberOfMessageParts() !== null) {
$maximumNumberOfMessageParts['maximumNumberOfMessageParts'] = $message->getMaximumNumberOfMessageParts();
}

$msg = $xml->addChild('MSG');
foreach ($message->toXmlArray() as $name => $value) {
$msg->addChild($name, (string) $value);
$reference = [];
if ($message->getReference() !== null) {
$reference['reference'] = $message->getReference();
}
$msg->addChild('TO', $recipient);

return $xml->asXML();
$json = [
'messages' => [
'authentication' => [
'productToken' => $this->productToken,
],
'msg' => [[
'body' => $body,
'to' => [[
'number' => $recipient,
]],
'dcs' => strtoupper($message->getEncodingDetectionType()) === 'AUTO' ? '0' : $message->getEncodingDetectionType(),
'from' => $message->getOriginator(),
...$minimumNumberOfMessageParts,
...$maximumNumberOfMessageParts,
...$reference,
]],
],
];

return json_encode($json);
}
}
50 changes: 32 additions & 18 deletions src/CmsmsMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class CmsmsMessage

protected string $reference = '';

protected int $tariff = 0;
protected string $encodingDetectionType = 'AUTO';

protected ?int $minimumNumberOfMessageParts = null;

Expand All @@ -31,6 +31,11 @@ public function body(string $body): self
return $this;
}

public function getBody(): string
{
return $this->body;
}

public function originator(string|int $originator): self
{
if (empty($originator) || strlen($originator) > 11) {
Expand All @@ -42,6 +47,11 @@ public function originator(string|int $originator): self
return $this;
}

public function getOriginator(): string
{
return $this->originator;
}

public function reference(string $reference): self
{
if (empty($reference) || strlen($reference) > 32 || ! ctype_alnum($reference)) {
Expand All @@ -53,16 +63,9 @@ public function reference(string $reference): self
return $this;
}

public function tariff(int $tariff): self
{
$this->tariff = $tariff;

return $this;
}

public function getTariff(): int
public function getReference(): string
{
return $this->tariff;
return $this->reference;
}

public function multipart(int $minimum, int $maximum): self
Expand All @@ -77,15 +80,26 @@ public function multipart(int $minimum, int $maximum): self
return $this;
}

public function toXmlArray(): array
public function getMinimumNumberOfMessageParts(): ?int
{
return $this->minimumNumberOfMessageParts;
}

public function getMaximumNumberOfMessageParts(): ?int
{
return $this->maximumNumberOfMessageParts;
}

public function encodingDetectionType(string|int $encodingDetectionType): self
{
$this->encodingDetectionType = (string) $encodingDetectionType;

return $this;
}

public function getEncodingDetectionType(): string
{
return array_filter([
'BODY' => $this->body,
'FROM' => $this->originator,
'REFERENCE' => $this->reference,
'MINIMUMNUMBEROFMESSAGEPARTS' => $this->minimumNumberOfMessageParts,
'MAXIMUMNUMBEROFMESSAGEPARTS' => $this->maximumNumberOfMessageParts,
]);
return $this->encodingDetectionType;
}

public static function create(string $body = ''): self
Expand Down
14 changes: 14 additions & 0 deletions src/Events/SMSSendingFailedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace NotificationChannels\Cmsms\Events;

use Illuminate\Foundation\Events\Dispatchable;

class SMSSendingFailedEvent
{
use Dispatchable;

public function __construct(public string $response)
{
}
}
14 changes: 14 additions & 0 deletions src/Events/SMSSentSuccessfullyEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace NotificationChannels\Cmsms\Events;

use Illuminate\Foundation\Events\Dispatchable;

class SMSSentSuccessfullyEvent
{
use Dispatchable;

public function __construct(public string $payload)
{
}
}
Loading

0 comments on commit 93e1dc1

Please sign in to comment.