# PHP SDK and Laravel package

paaltech/paalchat-php and paaltech/paalchat-laravel - typed errors, safe retries and verified callbacks.

> **In a nutshell:** composer require paaltech/paalchat-laravel, set PAALCHAT_API_KEY and PAALCHAT_WEBHOOK_SECRET. Writes get an Idempotency-Key automatically and are retried safely; errors are typed exceptions with error codes; callbacks arrive verified and de-duplicated as Laravel events.

## Install

```bash
composer require paaltech/paalchat-laravel   # Laravel (includes the PHP client)
composer require paaltech/paalchat-php       # any PHP 8.2+ project
```

```dotenv
PAALCHAT_API_KEY=sk_live_...        # sk_test_... for the sandbox
PAALCHAT_WEBHOOK_SECRET=...         # your webhook endpoint's secret
```

## Send

```php
use PaalTech\PaalChat\Laravel\Facades\PaalChat;

PaalChat::businesses()->upsert(['external_id' => 'presec', 'name' => 'Presec Legon']);

PaalChat::messages('presec')->sendTemplate(
    '+233241234567', 'fee_reminder', 'en_US',
    ['parent' => 'Mrs Mensah', 'student' => 'Kofi'],
    reference: 'notification-4411',
);
```

Without Laravel: `$paalchat = new PaalTech\PaalChat\PaalChat(getenv('PAALCHAT_API_KEY'));`
then the same calls on `$paalchat`.

| Area | Calls |
|---|---|
| Product | `me()`, `usage([...])` |
| Businesses | `businesses()->upsert()`, `get()`, `list()` |
| WhatsApp | `whatsapp($business)->connectLink()`, `status()`, `phones()`, `disconnect()` |
| Templates | `templates($business)->list()`, `create()`, `delete()` |
| Messages | `messages($business)->send()`, `sendText()`, `sendTemplate()`, `get()` |
| Media | `media($business)->upload($path)`, `get($id)` |
| Contacts | `contacts($business)->list()`, `upsert()`, `get()`, `update()` |
| Conversations | `conversations($business)->list()`, `get()`, `update()`, `messages()`, `addNote()` |
| Inbox | `inbox($business)->signIn($staff)`, `members()`, `removeAccess()` |
| Sandbox | `sandbox($business)->incoming()`, `status()` |

## Retries and errors

- Every write gets an `Idempotency-Key` (reused across its retries), so a retry never
  acts twice. Pass your own as the last argument of `send()` if you retry later yourself.
- Network errors, `429` (after `Retry-After`) and `5xx` are retried with backoff
  (`max_retries`, default 2).
- Errors are exceptions: `AuthenticationException` (401), `PermissionException` (403),
  `NotFoundException` (404), `ConflictException` (409), `ValidationException` (422, with
  `$e->errors`), `RateLimitException` (429, `$e->retryAfter`), `ServerException`,
  `ConnectionException`. Each has `$e->errorCode` and `$e->requestId`.

```php
use PaalTech\PaalChat\Exceptions\ValidationException;

try {
    PaalChat::messages('presec')->sendText('233241234567', 'Hello', reference: 'inbox-991');
} catch (ValidationException $e) {
    if ($e->errorCode === 'outside_service_window') {
        // offer a template instead
    }
}
```

## Callbacks

The Laravel package registers `POST /paalchat/callback` (set `PAALCHAT_WEBHOOK_PATH`
to change it; exclude it from CSRF). Each callback is verified on the raw body,
de-duplicated on `X-PaalChat-Delivery`, answered at once, and fired as
`PaalChatEventReceived` and `paalchat.<event>`:

```php
use Illuminate\Support\Facades\Event;
use PaalTech\PaalChat\Event as PaalChatEvent;

Event::listen('paalchat.message.status', function (PaalChatEvent $event) {
    Notification::where('id', $event->data['reference'])->first()?->advanceTo($event->data['status']);
});
```

Without Laravel, verify yourself:

```php
use PaalTech\PaalChat\Webhook;

$event = Webhook::constructEvent(file_get_contents('php://input'), $_SERVER['HTTP_X_PAALCHAT_SIGNATURE'] ?? '', $secret);
```
