# Provider adapter development guide

The Super Admin can add providers **without code** when the provider fits the generic HTTP model (adapter `custom`): base URL, endpoint, auth style, JSON/form body template with placeholders (`{{to}} {{text}} {{voice}} {{language}} {{reference}} {{caller_id}}`), response path mapping, webhook HMAC (header, prefix, optional timestamp), webhook/status/digit maps. No admin-supplied code is ever executed.

For a provider that needs a different protocol (OAuth, SOAP, multi-step, different signature scheme) write a reviewed adapter:

```php
namespace App\Providers\Voice;

class AcmeVoiceProvider extends AbstractHttpProvider   // or implement VoiceProviderInterface directly
{
    protected function buildPayload(CallRequest $r): array { return ['number' => $r->to, 'message' => $r->text, 'voice' => $r->voice, 'ref' => $r->reference]; }
    // override initiateCall(), verifyWebhook(), normalizeResponse(), getCallStatus(), testConnection() as needed
}
```
Register it in `config/voiceorder.php → adapters` (`'acme' => AcmeVoiceProvider::class`) and deploy; it then appears in the provider form's *Adapter / type* list. The WooCommerce plugin never changes.

## Contract
* `initiateCall(CallRequest)` → `ProviderResult` with **exactly one** outcome: `accepted | rejected | temporary_failure | ambiguous` (see `docs/ARCHITECTURE.md`). When in doubt choose `ambiguous` – the engine will reconcile instead of double-dialling and double-charging.
* `verifyWebhook($raw, $headers)` → `[bool, reason]`; use `hash_equals`; verify the **raw** body.
* `normalizeResponse(array)` → `NormalizedEvent` containing only fields the provider really sent. Do not invent ids. Never map a campaign id to `providerCallId`.
* `getCallStatus()` → `NormalizedEvent|null` (null = cannot tell).
* Log through `log()` (it redacts keys/secrets/phone numbers into `provider_call_logs`).
* Provider-specific knowledge stays inside the adapter; the engine only sees VoiceOrder statuses: `queued, processing, dispatching, accepted, ringing, answered, completed, no_answer, busy, failed, rejected, cancelled, unknown, awaiting_reconciliation`.

## Checklist for a new adapter
Auth works · test call heard in bn & en · digit 1 and 2 arrive and match the right call · duplicate delivery changes nothing · bad signature → 401 · timeout leaves `awaiting_reconciliation` and never redials · cost per call configured · add tests modelled on `tests/Feature/CallEngineTest.php`.
