Промты для ИИ-агентов

Если интеграцию пишет Claude Code, Cursor или другой агент, отдайте ему контекстный блок и готовый промт. Здесь именно тот текст, который стоит скопировать целиком.

Как этим пользоваться

  1. Скопируйте контекстный блок ниже в файл проекта — например docs/yespay.md, CLAUDE.md или .cursor/rules/yespay.md. Агент читает его сам, и ему не нужно угадывать формат наших полей.
  2. Возьмите нужный промт из списка и дайте его агенту вместе с этим файлом.
  3. Прогоните промт «Проверь интеграцию» на тестовом ключе sk_test_… до того, как включите боевой.
  4. Перед выходом в бой прогоните промт «Ревью перед боем».

Одна проверка, которую нельзя делегировать

Агент охотно напишет код, который считает unknown отказом, а редирект покупателя — подтверждением оплаты. Оба варианта выглядят рабочими на демо и теряют деньги в бою. Поэтому в промтах ниже эти правила заданы явно, а после генерации проверьте глазами ровно два места: где заказ становится оплаченным и что происходит при статусе unknown.

Контекстный блок

Полное описание API в форме, удобной для модели: адреса, форматы, правила повторов, сценарии песочницы и жёсткие ограничения. Не требует похода в интернет.

markdown
# yespay — контекст для ИИ-агента

yespay — REST API для приёма Kaspi Pay в Казахстане. Мерчант создаёт платёж,
покупатель платит в приложении Kaspi, деньги идут напрямую на счёт мерчанта в
Kaspi Business. yespay уведомляет мерчанта подписанным вебхуком.

БАЗОВЫЙ АДРЕС: https://api.getyespay.com
ДОКУМЕНТАЦИЯ: https://getyespay.com/docs и https://getyespay.com/docs/api

АВТОРИЗАЦИЯ
  Заголовок: Authorization: Bearer sk_live_...  (или X-API-Key: sk_live_...)
  sk_test_... — песочница на мок-провайдере; sk_live_... — реальные деньги.
  Секретный ключ — только на сервере. Никогда во фронтенде и не в репозитории.

ДЕНЬГИ И ФОРМАТЫ
  amount — целое число ТЕНГЕ (не тиын). 14990 == 14 990 ₸. Дробные отклоняются.
  Валюта — только KZT. Время — ISO 8601 UTC. Тело и ответы — JSON.
  Телефон покупателя — казахстанский мобильный, принимается +7…, 8…, 10 цифр.

ОСНОВНЫЕ ЭНДПОИНТЫ
  POST   /v1/payments                 создать платёж
  GET    /v1/payments/:id             прочитать платёж
  GET    /v1/payments?order_id=...    найти по заказу
  POST   /v1/payments/:id/cancel      отменить неоплаченный
  GET    /v1/payments/:id/events      история статусов + сырой статус Kaspi
  POST   /v1/refunds                  возврат (только полный)
  POST   /v1/webhooks                 создать эндпоинт вебхука
  POST   /v1/webhooks/test            тестовое событие

СОЗДАНИЕ ПЛАТЕЖА
  POST /v1/payments
  Headers: Authorization, Content-Type: application/json, Idempotency-Key
  Body: { "amount": 14990, "currency": "KZT",
          "customer": { "phone": "+77001234567" },
          "order_id": "ORDER-1001", "description": "Заказ №1001" }
  Ответ 201, status: "pending". Финальный статус приходит вебхуком.

ИДЕМПОТЕНТНОСТЬ
  Заголовок Idempotency-Key обязателен на POST /v1/payments и POST /v1/refunds.
  Повтор с тем же ключом возвращает тот же платёж (Idempotent-Replay: true).
  Тот же ключ с другим телом → 409 idempotency_key_reused.

ВЕБХУКИ
  Заголовки: X-Yespay-Signature: t=<unix>,v1=<hmac-sha256-hex>
             X-Yespay-Event-Id, X-Yespay-Delivery-Id, X-Yespay-Attempt
  Подписываемая строка: "<t>." + СЫРОЕ тело запроса. Ключ — секрет whsec_...
  Проверять: возраст события <= 300 c, сравнение подписи в постоянном времени.
  Дедупликация — по X-Yespay-Event-Id. Ответ 2xx быстрее 10 c, иначе до 6 повторов.
  Тело: { "id", "type", "createdAt", "livemode", "data" }.
  createdAt в теле события — camelCase; поля внутри data — snake_case.

СТАТУСЫ ПЛАТЕЖА
  created, pending, processing → не финальные
  paid, failed, expired, cancelled, refunded → финальные
  refund_pending, partially_refunded → промежуточные
  unknown → ИСХОД НЕИЗВЕСТЕН. Это НЕ отказ.

ЖЁСТКИЕ ПРАВИЛА (нарушение = деньги потеряны)
  1. unknown НИКОГДА не трактовать как неуспех. Заказ придержать, товар не
     отпускать, деньги не возвращать, ждать следующего события.
  2. Заказ помечать оплаченным ТОЛЬКО по вебхуку payment.paid или по чтению
     GET /v1/payments/:id — никогда по редиректу покупателя обратно на сайт.
  3. Сумму считать на сервере. Никогда не брать из тела запроса браузера.
  4. Idempotency-Key на каждом создании платежа и возврата.
  5. Подпись вебхука считать по сырым байтам тела, до JSON.parse.
  6. Секретный ключ и whsec_ — только в переменных окружения.

КОДЫ ОШИБОК, ВАЖНЫЕ ДЛЯ ЛОГИКИ ПОВТОРОВ
  Повторять с тем же Idempotency-Key: 429 rate_limited, 502 provider_rejected,
  503 provider_unavailable, 503 provider_session_evicted, сетевые таймауты.
  Не повторять: 4xx (кроме 409 idempotency_request_in_flight — подождать секунду).
  409 payment_account_reauth_required — кассир Kaspi отключился, нужен человек.

ТЕСТОВЫЙ РЕЖИМ (ключ sk_test_...)
  Сценарий выбирается двумя последними цифрами номера покупателя:
    ...00 → failed через ~5 c
    ...11 → unknown через ~5 c
    ...77 → навсегда pending
    ...99 → expired через ~20 c
    иначе → paid через ~8 c

Промты

ПромтКогда использовать
Подключение к проектуПервая интеграция: сервисный слой, создание платежа, хранение связи с заказом.
Обработчик вебхуковОтдельно от первого — это место, где чаще всего ошибаются.
Проверка интеграцииПрогон всех веток на тестовом ключе перед включением боевого.
Ревью перед боемФинальная проверка чужого или своего кода по чек-листу.
Оплата в боте или агентеКогда платёж инициирует сам ИИ-агент в диалоге с покупателем.

1. Подключение к проекту

text
Подключи приём оплаты Kaspi Pay через yespay в этот проект.

Сначала прочитай контекст yespay, который я приложил ниже (или открой
https://getyespay.com/docs/api), и только потом пиши код. Не выдумывай эндпоинты и
поля, которых там нет.

Что нужно сделать:
1. Сервис-обёртку над API yespay: создание платежа, чтение платежа по id,
   поиск по order_id, возврат. Ключ читать из переменной окружения
   YESPAY_SECRET_KEY, базовый адрес — из YESPAY_API_BASE со значением по
   умолчанию https://api.getyespay.com.
2. Создание платежа при оформлении заказа. Сумму считать на сервере из
   корзины/прайса, а не брать из запроса клиента. В Idempotency-Key класть
   идентификатор заказа.
3. Хранение связи заказ ↔ payment_id в нашей базе, вместе со статусом и
   временем последнего обновления.
4. Обработчик вебхука по отдельному промту (см. следующий шаг) — здесь только
   оставь место под него.
5. Повторы: 429/502/503 и сетевые ошибки — экспоненциальная выдержка, тот же
   Idempotency-Key, не больше 5 попыток. 4xx не повторять.

Ограничения:
- Не помечай заказ оплаченным нигде, кроме обработки payment.paid или явного
  чтения платежа из API.
- Статус unknown не считай отказом: заказ переводи в состояние «нужна проверка»
  и не отпускай товар.
- Секреты не хардкодь и не логируй; телефон покупателя в логах маскируй.

Покажи изменения по файлам и объясни, где именно заказ становится оплаченным.

2. Обработчик вебхуков

Отдельным промтом намеренно: проверка подписи, дедупликация и реакция на unknown — это три места, где ошибка не видна на тесте и стоит денег в бою.

text
Напиши обработчик вебхуков yespay для этого проекта.

Требования к проверке подписи:
- Читай СЫРОЕ тело запроса. Фреймворковый JSON-парсер для этого маршрута
  отключи — после JSON.parse/повторной сериализации подпись не сойдётся.
- Заголовок X-Yespay-Signature имеет вид "t=<unix>,v1=<hex>".
- Ожидаемая подпись = HMAC-SHA256(secret, "<t>." + сырое тело), hex.
  secret — из переменной окружения YESPAY_WEBHOOK_SECRET.
- Отклоняй событие, если |now - t| > 300 секунд.
- Сравнивай подписи в постоянном времени.
- При неудачной проверке — 400 и ничего не выполнять.

Требования к обработке:
- Дедупликация по заголовку X-Yespay-Event-Id: сохраняй обработанные id и на
  повтор отвечай 200, не выполняя работу второй раз.
- Обрабатывай типы: payment.paid, payment.failed, payment.expired,
  payment.cancelled, payment.status_unknown, refund.succeeded,
  payment_account.reauth_required.
- payment.paid: пометь заказ оплаченным. Перед этим сверь data.amount с
  суммой заказа в нашей базе; при расхождении — не помечай, заведи алерт.
- payment.status_unknown: НЕ отказ. Переведи заказ в состояние «нужна ручная
  проверка» и уведоми ответственного. Товар не отпускать, деньги не возвращать.
- payment_account.reauth_required: это операционный инцидент — уведомление
  человеку, а не запись в лог.
- Отвечай 2xx быстрее 10 секунд: тяжёлую работу ставь в очередь.
- Неизвестный тип события — отвечай 200 и игнорируй, не падай.

Добавь тесты: верная подпись, испорченная подпись, просроченный timestamp,
повторная доставка того же event id, неизвестный тип события.

3. Проверка интеграции в песочнице

text
Проверь интеграцию с yespay в тестовом режиме и покажи результат.

Используй ключ sk_test_... из окружения. Сценарий выбирается двумя последними
цифрами номера покупателя, поэтому прогони все ветки:
  +77001234500 → ожидается failed примерно через 5 секунд
  +77001234511 → ожидается unknown примерно через 5 секунд
  +77001234577 → остаётся pending навсегда
  +77001234599 → ожидается expired примерно через 20 секунд
  +77001234567 → ожидается paid примерно через 8 секунд

Для каждой ветки:
1. Создай платёж через наш сервисный слой (не curl-ом мимо кода).
2. Дождись финального статуса: либо через наш обработчик вебхуков, либо опросом
   GET /v1/payments/:id не чаще раза в 3 секунды, максимум 60 секунд.
3. Проверь, в каком состоянии оказался заказ в нашей базе.
4. Отдельно проверь ветку unknown: убедись, что заказ НЕ отменён и НЕ отгружен,
   а помечен как требующий проверки.

Дополнительно:
- Отправь POST /v1/webhooks/test и убедись, что проверка подписи проходит.
- Повтори доставку одного и того же события дважды и убедись, что второй раз
  ничего не выполняется.
- Дважды отправь создание платежа с одинаковым Idempotency-Key и проверь, что
  платёж один.

Отчитайся таблицей: ветка → ожидание → факт → прошло/нет. Не пиши «работает»
про то, что не выполнялось.

4. Ревью перед боем

text
Сделай ревью нашей интеграции с yespay перед выходом в бой.
Проверь по пунктам и на каждый дай вердикт с ссылкой на файл и строку.

1. Секретный ключ используется только на сервере, не попадает в бандл
   фронтенда, в мобильное приложение и в репозиторий.
2. Idempotency-Key передаётся на каждом POST /v1/payments и POST /v1/refunds,
   и он детерминирован от заказа (ретрай даёт тот же ключ).
3. Подпись вебхука считается по сырому телу, проверяется возраст события
   (<= 300 c) и сравнение идёт в постоянном времени.
4. Обработчик вебхука идемпотентен по X-Yespay-Event-Id.
5. Заказ помечается оплаченным только по payment.paid или по чтению платежа из
   API, а не по редиректу покупателя.
6. Сумма заказа сверяется с data.amount из события.
7. Статус unknown нигде не трактуется как отказ, отмена или повод вернуть деньги.
8. Обработка payment_account.reauth_required доходит до человека.
9. Повторы делаются только для 429/502/503 и сетевых ошибок, с выдержкой.
10. Телефоны и секреты не попадают в логи.

Формат: пункт → статус (ок / нарушено / не применимо) → где → что исправить.
Если чего-то в коде нет, так и скажи, не додумывай.

5. Оплата внутри бота или агента

Если платёж инициирует сам ИИ-агент, важно ограничить его полномочия: сумма приходит из вашего прайса, возврат агенту недоступен, а unknown ведёт к оператору, а не к отказу.

text
Подключи оплату Kaspi через yespay к моему боту/агенту как инструмент.

Опиши агенту ровно два действия и ничего сверх:
  create_payment(amount_kzt: int, phone: str, order_id: str, description: str)
    → POST /v1/payments с Idempotency-Key = order_id, возвращает payment_id и статус
  get_payment(payment_id: str)
    → GET /v1/payments/:id, возвращает статус и сумму

Правила, которые агент нарушать не должен:
- Сумму агент не выбирает сам: она приходит из прайса/корзины на нашей стороне.
- Агент не имеет доступа к возвратам. Возврат делает человек.
- Ответ «оплачено» агент даёт только при status == "paid".
- При status == "unknown" агент отвечает «оплата уточняется» и передаёт диалог
  оператору. Не «не оплачено».
- Ключ sk_live_... находится в серверном коде инструмента, агент его не видит и
  не может подставить в запрос сам.

Что агенты ломают чаще всего

ОшибкаЧем оборачиваетсяКак в промте это закрыто
Подпись считается по разобранному и заново собранному JSONВсе вебхуки отклоняются, оплаты не доходят до заказаТребование читать сырое тело и отключить парсер на маршруте
unknown обрабатывается как отказОтмена оплаченного заказа или лишний возвратЯвное правило: придержать заказ, позвать человека
Заказ помечается оплаченным по редиректу покупателяЗаказ «оплачен» без денег: страницу можно открыть рукамиОплата фиксируется только по вебхуку или чтению платежа
Idempotency-Key генерируется случайным на каждую попыткуРетрай очереди выставляет покупателю второй счётКлюч детерминирован от заказа
Сумма берётся из запроса браузераПокупатель платит сколько захочетСумма считается на сервере из прайса
Повтор на 4xxБесполезные запросы и упор в лимитПовторы только для 429/502/503 и сетевых ошибок

Дальше

Пошаговая инструкция для человека — «Как подключить yespay». Полный справочник полей и ошибок — API-документация.