Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ $data = json_decode($response->getBody()->getContents(), true);
|----------------------|--------|------------------------------|
| `GetContacts` | GET | `/api/v1/getContacts` |
| `GetMessageTemplates`| GET | `/api/v1/getMessageTemplates`|
| `SendTemplateMessage`| POST | `/api/v1/sendTemplateMessage`|

## Testing

Expand All @@ -58,6 +59,8 @@ src/
├── GetContactsData.php
├── GetMessageTemplates.php
├── GetMessageTemplatesData.php
├── SendTemplateMessage.php
├── SendTemplateMessageData.php
└── Dto/
├── Contact.php
└── MessageTemplate.php
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class ContactController
| Class | Method | Endpoint |
|-----------------------|--------|-------------------------------|
| `GetMessageTemplates` | GET | `/api/v1/getMessageTemplates` |
| `SendTemplateMessage` | POST | `/api/v1/sendTemplateMessage` |

## Usage

Expand Down
18 changes: 9 additions & 9 deletions src/Api/Dto/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ public function __construct(
public static function fromArray(array $data): self
{
return new self(
id: data_get_str($data, 'id', ''),
phone: data_get_str($data, 'phone', ''),
fullName: data_get_str($data, 'fullName', ''),
wAid: is_string($v = data_get_str($data, 'wAid')) ? $v : null,
firstName: is_string($v = data_get_str($data, 'firstName')) ? $v : null,
email: is_string($v = data_get_str($data, 'email')) ? $v : null,
contactStatus: is_string($v = data_get_str($data, 'contactStatus')) ? $v : null,
created: is_string($v = data_get_str($data, 'created')) ? $v : null,
lastUpdated: is_string($v = data_get_str($data, 'lastUpdated')) ? $v : null,
id: data_get_str($data, 'id') ?? '',
phone: data_get_str($data, 'phone') ?? '',
fullName: data_get_str($data, 'fullName') ?? '',
wAid: data_get_str($data, 'wAid'),
firstName: data_get_str($data, 'firstName'),
email: data_get_str($data, 'email'),
contactStatus: data_get_str($data, 'contactStatus'),
created: data_get_str($data, 'created'),
lastUpdated: data_get_str($data, 'lastUpdated'),
);
}
}
12 changes: 6 additions & 6 deletions src/Api/Dto/MessageTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ public function __construct(
public static function fromArray(array $data): self
{
return new self(
id: data_get_str($data, 'id', ''),
elementName: data_get_str($data, 'elementName', ''),
category: is_string($v = data_get_str($data, 'category')) ? $v : null,
status: is_string($v = data_get_str($data, 'status')) ? $v : null,
language: is_string($v = data_get_str($data, 'language')) ? $v : null,
body: is_string($v = data_get_str($data, 'body')) ? $v : null,
id: data_get_str($data, 'id') ?? '',
elementName: data_get_str($data, 'elementName') ?? '',
category: data_get_str($data, 'category'),
status: data_get_str($data, 'status'),
language: data_get_str($data, 'language'),
body: data_get_str($data, 'body'),
);
}
}
12 changes: 6 additions & 6 deletions src/Api/GetContactsData.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public static function fromResponse(ResponseInterface $response): self
$link = $data['link'] ?? [];

return new self(
result: is_string($data['result'] ?? null) ? $data['result'] : '',
total: (int) ($link['total'] ?? 0),
pageNumber: (int) ($link['pageNumber'] ?? 1),
pageSize: (int) ($link['pageSize'] ?? 50),
prevPage: is_string($link['prevPage'] ?? null) ? $link['prevPage'] : null,
nextPage: is_string($link['nextPage'] ?? null) ? $link['nextPage'] : null,
result: data_get_str($data, 'result') ?? '',
total: data_get_int($link, 'total'),
pageNumber: data_get_int($link, 'pageNumber', 1),
pageSize: data_get_int($link, 'pageSize', 50),
prevPage: data_get_str($link, 'prevPage'),
nextPage: data_get_str($link, 'nextPage'),
contacts: array_map(
Contact::fromArray(...),
$data['contact_list'] ?? []
Expand Down
12 changes: 6 additions & 6 deletions src/Api/GetMessageTemplatesData.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public static function fromResponse(ResponseInterface $response): self
$link = $data['link'] ?? [];

return new self(
result: is_string($data['result'] ?? null) ? $data['result'] : '',
total: (int) ($link['total'] ?? 0),
pageNumber: (int) ($link['pageNumber'] ?? 1),
pageSize: (int) ($link['pageSize'] ?? 50),
prevPage: is_string($link['prevPage'] ?? null) ? $link['prevPage'] : null,
nextPage: is_string($link['nextPage'] ?? null) ? $link['nextPage'] : null,
result: data_get_str($data, 'result') ?? '',
total: data_get_int($link, 'total'),
pageNumber: data_get_int($link, 'pageNumber', 1),
pageSize: data_get_int($link, 'pageSize', 50),
prevPage: data_get_str($link, 'prevPage'),
nextPage: data_get_str($link, 'nextPage'),
messageTemplates: array_map(
MessageTemplate::fromArray(...),
$data['messageTemplates'] ?? []
Expand Down
39 changes: 39 additions & 0 deletions src/Api/SendTemplateMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Wati\Api;

use Wati\Http\WatiRequest;

final class SendTemplateMessage extends WatiRequest
{
/**
* @param string $whatsappNumber The recipient's WhatsApp number
* @param string $templateName The name of the template to send
* @param string $broadcastName The broadcast/campaign name
* @param array<array{name: string, value: string}> $parameters Template parameters
*/
public function __construct(
public readonly string $whatsappNumber,
public readonly string $templateName,
public readonly string $broadcastName,
public readonly array $parameters = []
) {
$body = json_encode([
'template_name' => $templateName,
'broadcast_name' => $broadcastName,
'parameters' => $parameters,
]) ?: '{}';

parent::__construct(
'POST',
"/api/v1/sendTemplateMessage?whatsappNumber={$whatsappNumber}",
[
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
$body
);
}
}
42 changes: 42 additions & 0 deletions src/Api/SendTemplateMessageData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Wati\Api;

use Psr\Http\Message\ResponseInterface;

final readonly class SendTemplateMessageData
{
public function __construct(
public bool $result,
public ?string $message = null,
public ?string $id = null,
public ?string $phone = null,
) {}

public static function fromResponse(ResponseInterface $response): self
{
/**
* @var array{
* result?: bool,
* message?: string|null,
* id?: string|null,
* phone?: string|null,
* } $data
*/
$data = json_decode($response->getBody()->getContents(), true) ?? [];

return new self(
result: data_get_bool($data, 'result'),
message: data_get_str($data, 'message'),
id: data_get_str($data, 'id'),
phone: data_get_str($data, 'phone'),
);
}

public function isSuccess(): bool
{
return $this->result;
}
}
65 changes: 57 additions & 8 deletions src/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,70 @@
*
* @param array<string, mixed> $data
* @param TDefault $default
* @return string|TDefault
* @return mixed|TDefault
*/
function data_get_str(array $data, string $key, mixed $default = null): mixed
function data_get_value(array $data, string $key, mixed $default = null): mixed
{
if (! array_key_exists($key, $data) || $data[$key] === null) {
return $default;
return array_key_exists($key, $data) && $data[$key] !== null
? $data[$key]
: $default;
}

/**
* @param array<string, mixed> $data
*/
function data_get_str(array $data, string $key, ?string $default = null): ?string
{
$value = data_get_value($data, $key, $default);

if (is_string($value)) {
return trim($value);
}

if (is_scalar($value)) {
return (string) $value;
}

$value = $data[$key];
return $default;
}

/**
* @param array<string, mixed> $data
*/
function data_get_int(array $data, string $key, int $default = 0): int
{
$value = data_get_value($data, $key, $default);

return is_numeric($value) ? (int) $value : $default;
}

/**
* @param array<string, mixed> $data
*/
function data_get_bool(array $data, string $key, bool $default = false): bool
{
$value = data_get_value($data, $key, $default);

if (is_bool($value)) {
return $value;
}

// Handle common truthy/falsy string representations
if (is_string($value)) {
$value = trim($value);
return match (strtolower(trim($value))) {
'true', '1', 'yes', 'on' => true,
'false', '0', 'no', 'off' => false,
default => $default,
};
}

return $value !== '' ? $value : $default;
if (is_int($value)) {
return match ($value) {
1 => true,
0 => false,
default => $default,
};
}

return $value;
return $default;
}
110 changes: 110 additions & 0 deletions tests/Api/Dto/ContactTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

namespace Tests\Api\Dto;

use Wati\Api\Dto\Contact;

describe('Contact', function (): void {
it('can be created from array with all fields', function (): void {
$data = [
'id' => '123',
'phone' => '+1234567890',
'fullName' => 'John Doe',
'wAid' => 'wa123',
'firstName' => 'John',
'email' => 'john@example.com',
'contactStatus' => 'active',
'created' => '2024-01-01',
'lastUpdated' => '2024-01-02',
];

$contact = Contact::fromArray($data);

expect($contact->id)->toBe('123')
->and($contact->phone)->toBe('+1234567890')
->and($contact->fullName)->toBe('John Doe')
->and($contact->wAid)->toBe('wa123')
->and($contact->firstName)->toBe('John')
->and($contact->email)->toBe('john@example.com')
->and($contact->contactStatus)->toBe('active')
->and($contact->created)->toBe('2024-01-01')
->and($contact->lastUpdated)->toBe('2024-01-02');
});

it('can be created from array with required fields only', function (): void {
$data = [
'id' => '123',
'phone' => '+1234567890',
'fullName' => 'John Doe',
];

$contact = Contact::fromArray($data);

expect($contact->id)->toBe('123')
->and($contact->phone)->toBe('+1234567890')
->and($contact->fullName)->toBe('John Doe')
->and($contact->wAid)->toBeNull()
->and($contact->firstName)->toBeNull()
->and($contact->email)->toBeNull()
->and($contact->contactStatus)->toBeNull()
->and($contact->created)->toBeNull()
->and($contact->lastUpdated)->toBeNull();
});

it('handles empty array with defaults', function (): void {
$contact = Contact::fromArray([]);

expect($contact->id)->toBe('')
->and($contact->phone)->toBe('')
->and($contact->fullName)->toBe('');
});

it('trims string values', function (): void {
$data = [
'id' => ' 123 ',
'phone' => ' +1234567890 ',
'fullName' => ' John Doe ',
'email' => ' john@example.com ',
];

$contact = Contact::fromArray($data);

expect($contact->id)->toBe('123')
->and($contact->phone)->toBe('+1234567890')
->and($contact->fullName)->toBe('John Doe')
->and($contact->email)->toBe('john@example.com');
});

it('handles null values in optional fields', function (): void {
$data = [
'id' => '123',
'phone' => '+1234567890',
'fullName' => 'John Doe',
'wAid' => null,
'firstName' => null,
'email' => null,
];

$contact = Contact::fromArray($data);

expect($contact->wAid)->toBeNull()
->and($contact->firstName)->toBeNull()
->and($contact->email)->toBeNull();
});

it('converts scalar values to strings', function (): void {
$data = [
'id' => 123,
'phone' => 1234567890,
'fullName' => ['John', 'Doe'],
];

$contact = Contact::fromArray($data);

expect($contact->id)->toBe('123')
->and($contact->phone)->toBe('1234567890')
->and($contact->fullName)->toBe('');
});
});
Loading