Перейти к содержимому
AMIO
Меню

AMIO для разработчиков

Подключите кассу, интернет-магазин или банковскую систему к AMIO. API дает те же результаты, что и работа в приложении, с теми же правилами, ролями и журналом аудита.

Спецификация OpenAPI

Аутентификация

Администратор вашей организации создает API-ключ в приложении, в разделе «Разработчикам». Передавайте его в заголовке Authorization: Bearer amio_live_... с каждым запросом к /api/public/v1. Ключ показывается только один раз и может быть отозван в любой момент.

API предназначен для серверов, а не для браузеров: CORS отключен, и ключ нельзя помещать в код, который выполняется на устройстве покупателя.

curl https://www.getamio.com/api/public/v1/quotes \
  -H "Authorization: Bearer amio_live_..." \
  -H "Idempotency-Key: 6f1c2f9e-6d0b-4f7e-9a51-2c8f2f4b7a10" \
  -H "Content-Type: application/json" \
  -d '{"amount":{"amountMinor":"12990000","currency":"RSD"},
       "description":"Laptop","instalments":[6,12]}'

Роли и права

У каждого ключа одна роль вашей организации, выбранная при создании, и он может только то, что эта роль может в приложении. Ключ может быть и только для чтения. Продавцы: администратор, менеджер и сотрудник; партнеры: администратор, утверждающий и аналитик.

Ключ действует от имени сотрудника, который его создал, и перестает работать, когда этот сотрудник покидает команду. Лимит 120 запросов в минуту на ключ; сверх этого вы получаете 429 и заголовок Retry-After.

Идемпотентность

Каждый POST и PUT должен содержать заголовок Idempotency-Key, например UUID. Повторный запрос с тем же ключом и тем же телом возвращает тот же ответ (с заголовком Idempotent-Replayed), поэтому предложение или возврат никогда не создаются дважды. Тот же ключ с другим телом возвращает 409 IDEMPOTENCY_CONFLICT. Ключи хранятся 24 часа.

Ошибки и суммы

Ошибки всегда одного вида: error.code, error.message и error.correlationId, тот же идентификатор есть в заголовке X-Correlation-Id. Укажите его при обращении в поддержку. Изменения используют версии: передавайте version, полученную последней; если что-то изменилось, вы получите 409 с причиной VERSION_CONFLICT.

Суммы всегда объект { amountMinor, currency }, где amountMinor целое число в наименьшей единице валюты, записанное как текст, например "12990000" для 129 900,00 RSD.

{
  "error": {
    "code": "CONFLICT",
    "message": "VERSION_CONFLICT",
    "reason": "VERSION_CONFLICT",
    "correlationId": "c0a8..."
  }
}

Webhook-уведомления

Администратор добавляет адрес и выбирает события. AMIO отправляет POST с JSON-телом { id, type, createdAt, livemode, data } при изменении статуса. Ответьте статусом 2xx в течение 10 секунд; неудачные доставки повторяются с растущими интервалами, последние попытки видны в приложении.

У каждого сообщения есть заголовок AMIO-Signature: t=<unix-время>,v1=<hex HMAC-SHA256 от "t.тело">, подписанный секретом адреса. Проверяйте подпись по исходному телу и отклоняйте старые сообщения. Одно событие может прийти несколько раз; используйте id, чтобы обработать его один раз.

  • Продавцы: quote.created, quote.status_changed, application.status_changed, refund.status_changed
  • Партнеры: application.status_changed, deferral.requested, deferral.decided
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (a string), before JSON.parse.
export function verifyAmioWebhook(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(
    String(header ?? "").split(",").map((part) => part.trim().split("=", 2)),
  );
  const timestamp = Number(parts.t);
  if (!parts.v1 || !Number.isInteger(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1, "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

// Express: app.post("/amio/webhooks", express.text({ type: "*/*" }), (req, res) => {
//   if (!verifyAmioWebhook(req.body, req.get("AMIO-Signature"), process.env.AMIO_WEBHOOK_SECRET))
//     return res.sendStatus(400);
//   const event = JSON.parse(req.body); // { id, type, createdAt, livemode, data }
//   res.sendStatus(200);
// });

Встроенные экраны

Экраны нового предложения и статуса предложения можно показать в вашем портале в iframe: /{locale}/embed/merchant/quotes/new и /{locale}/embed/merchant/quotes/{id}. Сначала администратор добавляет адрес портала; на всех остальных адресах браузер их блокирует (Content-Security-Policy: frame-ancestors).

Браузеры не передают cookie входа AMIO в iframe на другом сайте, поэтому ваш сервер запрашивает одноразовый токен: POST /api/public/v1/embed-tokens. Токен действует 5 минут, и при первом открытии AMIO меняет его на короткую сессию только для вашей компании, с ролью ключа. Если портал на том же сайте, что и AMIO, работает и обычный вход (откройте его во всплывающем окне).

// On your server, with a merchant key:
const response = await fetch("https://www.getamio.com/api/public/v1/embed-tokens", {
  method: "POST",
  headers: {
    Authorization: "Bearer amio_live_...",
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ locale: "sr-Latn-RS" }), // or { quoteId } for one quote's status
});
const { url } = await response.json();

// In your portal page (an origin registered under Developers):
// <iframe src="{url}" width="480" height="820" title="AMIO"></iframe>

Тестовая среда

Ключи, начинающиеся с amio_test_, существуют только в тестовой среде и работают там с тестовым партнером. В рабочей среде используются только ключи amio_live_. Предложения продавца, который еще не активен, всегда помечены как тестовые (mode SANDBOX, livemode false в webhook-сообщениях).