> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paymove.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> ## paymove integration rules for AI agents
>
> These rules are verified against the live API. Follow them exactly.
>
> 1. Amounts are INTEGERS in grosze (minor units). `1000` means 10.00 PLN. PLN is the only supported currency and there is no currency field in the API.
> 2. A float `price` (e.g. `12.99`) is SILENTLY TRUNCATED to 12 grosze and still returns HTTP 200. Always send an integer: `Math.round(pln * 100)`.
> 3. Authenticate with the `X-API-KEY` header. Never `Authorization: Bearer`. Sandbox keys start with `sk_test_`, production keys with `sk_live_`.
> 4. Server-side only. A call from a merchant page is rejected. Never put the key in frontend code.
> 5. Unknown request fields are silently ignored and still return HTTP 200 — a wrong body shape looks like success. Match the documented shape exactly.
> 6. A missing `externalId` returns HTTP 500 `Something went wrong`, not 400. A missing `price` or `details.returnUrl` returns HTTP 200 and a usable `redirectUrl` — no error at all. Validate the body yourself before sending it.
> 7. Always verify the `X-Paymove-Signature` header on incoming webhooks before trusting them: https://docs.paymove.io/en/webhook-signature.md
> 8. Errors are `{"status": <int>, "message": "<text>"}`. Branch on the HTTP status only — never on `message`, which is unstable and leaks internal class names.
> 9. There is no rate limiting, no HTTP 429, no HTTP 422, no `Idempotency-Key` header and no API versioning. Do not write code that handles them.
> 10. Re-POSTing an `externalId` that already exists returns HTTP 200 with the ORIGINAL `redirectUrl` and silently discards EVERY field you send — the new price, description and details are all ignored. Use `PATCH /api/pay/product/{productId}/subproduct/{externalId}` to change a price.
> 11. Webhooks fire only on `COMPLETED` by default, and `retries` defaults to `0` (no retries) unless you set it explicitly. Delivery counts as successful when the HTTP status equals `expectedCode` — the response body is never inspected.
> 12. Never fulfil an order on the `returnUrl` redirect. The checkout does not redirect there by itself: the customer has to click "back to shop", and inside the widget modal that redirect never happens. Fulfil only in the webhook handler, after verifying the signature.
> 13. Do not pin a version of `@paymove-io/sdk` — install the latest.
> 14. Full documentation index: https://docs.paymove.io/llms.txt · Copy-paste quickstart: https://docs.paymove.io/en/quickstart.md · Agent skill: https://docs.paymove.io/skill.md

# ElevenLabs

Agent Panel osadza widget [ElevenLabs Conversational AI](https://elevenlabs.io/conversational-ai),
dzięki czemu użytkownik może porozmawiać głosowo z agentem sprzedażowym, który sprawdza
katalog produktów i finalizuje płatność BLIK - bez wpisywania czegokolwiek w interfejsie.

## 1. Osadzenie widgetu

W aplikacji ładowany jest skrypt embed ElevenLabs, a widget montowany jest z `dynamic-variables`
przekazującymi `agentId` portfela, do którego przypisana jest rozmowa:

```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
<script src="https://unpkg.com/@elevenlabs/convai-widget-embed" async></script>

<elevenlabs-convai
  agent-id="<id-agenta-elevenlabs>"
  dynamic-variables='{"agentId":"<agentId-portfela>"}'
></elevenlabs-convai>
```

`agentId` trafia do agenta ElevenLabs jako zmienna dynamiczna i jest przekazywany dalej w
każdym wywołaniu narzędzi płatniczych - to on wiąże rozmowę głosową z konkretnym portfelem.

<Warning>
  To są dwa różne identyfikatory o podobnej nazwie - łatwo je pomylić:

  * **`agent-id`** (atrybut widgetu) - ID bota w ElevenLabs. Wskazuje, którą konfigurację
    agenta głosowego załadować (prompt, głos, narzędzia). Stała wartość, ta sama dla każdej
    rozmowy.
  * **`agentId`** (wewnątrz `dynamic-variables`) - identyfikator portfela Paymove (ten sam
    co w `agentId` transakcji/agenta w Agent Panel). Zmienia się w zależności od tego, kto
    otwiera czat, i to on trafia do `process_blik_payment`, żeby narzędzie wiedziało, z
    którego portfela zejść płatność.
</Warning>

## 2. Konfiguracja narzędzi (webhook tools)

W panelu ElevenLabs → agent → **Narzędzia** skonfiguruj trzy narzędzia webhook wskazujące na
Twoje środowisko Agent Panel (np. `https://<twoja-domena>`):

### `get_available_products`

|        |                               |
| ------ | ----------------------------- |
| Metoda | `GET`                         |
| URL    | `/api/products`               |
| Auth   | brak - katalog jest publiczny |

Zwraca listę produktów z `id`, `name` i `price` (**w groszach**).

### `process_blik_payment`

|        |                      |
| ------ | -------------------- |
| Metoda | `POST`               |
| URL    | `/api/payments/blik` |

Body (JSON):

| Pole                | Źródło                     | Opis                                                                 |
| ------------------- | -------------------------- | -------------------------------------------------------------------- |
| `agentId`           | dynamic variable           | Portfel, z którego realizowana jest płatność                         |
| `price`             | z `get_available_products` | Cena **w groszach** - bez przeliczania na PLN                        |
| `email`             | LLM / rozmowa              | E-mail klienta                                                       |
| `authorizationCode` | LLM / rozmowa              | 6-cyfrowy kod BLIK podany przez klienta                              |
| `productId`         | z `get_available_products` | Identyfikator produktu (potrzebny, żeby paragon miał nazwę produktu) |

Odpowiedź:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "continueUrl": "https://...",
  "externalId": "agent_123abc456",
  "txId": "blik_9f2e...",
  "amount": 3000,
  "currency": "PLN"
}
```

<Warning>
  Ta odpowiedź **nie zawiera jeszcze wyniku płatności** - `externalId` służy wyłącznie do
  sprawdzenia statusu w kolejnym kroku. Agent nie powinien na tej podstawie ogłaszać sukcesu.
</Warning>

### `check_payment_status`

|             |                                                           |
| ----------- | --------------------------------------------------------- |
| Metoda      | `GET`                                                     |
| URL         | `/api/payments/blik/status`                               |
| Query param | `externalId` - **zmienna dynamiczna**, nie pytanie do LLM |

Odpowiedź:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{ "status": "pending" | "completed" | "failed", "paymove": { "status": "...", "orderId": "..." } }
```

## 3. Przechwycenie `externalId` jako dynamic variable

`externalId` powstaje dopiero w odpowiedzi `process_blik_payment`, więc w konfiguracji tego
narzędzia w sekcji **przypisań (assignments)** dodaj:

* pole odpowiedzi `externalId` → zapisz jako dynamic variable, np. `payment_external_id`

Następnie w `check_payment_status` parametr `externalId` ustaw jako tę samą zmienną
dynamiczną (`{{payment_external_id}}`), a nie jako podpowiedź LLM.

## 4. Kontrakt groszowy

Wszystkie kwoty w tych trzech narzędziach są w **groszach** (liczba całkowita, `30 zł` =
`3000`). Cena pobrana z `get_available_products` trafia bez żadnej modyfikacji do
`process_blik_payment.price`. To samo dotyczy pola `amount` w odpowiedzi - agent powinien
podzielić je przez 100, żeby wypowiedzieć cenę klientowi w złotówkach.

<Warning>
  W opisie parametru `price` w konfiguracji narzędzia `process_blik_payment` w panelu
  ElevenLabs upewnij się, że jest napisane wprost, że jednostką są **grosze** (np. "Cena w
  groszach - 30 zł należy wysłać jako 3000"). Sam opis w promptcie/systemie nie wystarczy,
  jeśli opis parametru narzędzia sugeruje PLN - LLM kieruje się przede wszystkim opisem pola.
</Warning>

## 5. System prompt - obsługa statusu

Dodaj do system promptu jednoznaczną instrukcję, że wynik płatności rozstrzyga wyłącznie pole
`status` z `check_payment_status`, a nie sam fakt, że wywołanie się powiodło:

```
Po process_blik_payment ZAWSZE wywołuj check_payment_status, dopóki status nie przestanie
być "pending" (maks. 10 razy, co 2-3 sekundy). Nigdy nie informuj klienta o wyniku płatności
przed otrzymaniem statusu innego niż "pending".

- status "completed" → potwierdź klientowi sukces zakupu i podaj nazwę produktu oraz cenę.
- status "failed" → poinformuj klienta, że płatność się nie powiodła, zaproponuj podanie
  nowego kodu BLIK.
- status "pending" → powiedz, że płatność się przetwarza, i sprawdź ponownie.
```

<Info>
  Agent Panel niezależnie śledzi te same transakcje i wyświetla użytkownikowi wiadomości o
  postępie zakupu głosowego w czacie - `check_payment_status` w ElevenLabs jest potrzebny,
  żeby **agent głosowy** wiedział, co powiedzieć klientowi, a nie po to, żeby zainicjować
  samą płatność.
</Info>
