{
  "service": "i-Free Money Mock Service — Callisto Merchant API (разовые платежи)",
  "purpose": "Тест-векторы подписи HMAC-SHA256 v3. Реализуйте функцию подписи и прогоните эти векторы ДО первого сетевого вызова. Если хотя бы один вектор не сошёлся — не отправляйте запрос: сравните ваш inner и outer_message с напечатанными, различие будет в формате timestamp, порядке конкатенации, регистре hex или в том, что inner передан сырыми байтами. / Implement the signature function and run these vectors BEFORE the first network call. If any vector fails, do not send the request: compare your inner and outer_message with the printed ones.",
  "algorithm": {
    "formula": "inner = lowercase(hex(HMAC_SHA256(key=secret, message=timestamp))); signature = lowercase(hex(HMAC_SHA256(key=secret, message=login + inner)))",
    "encoding": "UTF-8. Ключ и сообщения — строки; inner подставляется во второй проход как hex-строка из 64 символов, НЕ как сырые байты.",
    "timestamp_format_xml": "yyyyMMddHHmmss UTC (элемент <Header><Authentication><NowUtc>)",
    "timestamp_format_json": "ISO-8601 UTC, например 2026-02-10T17:56:37Z (заголовок X-Timestamp)",
    "timestamp_window_minutes": 20,
    "body_is_signed": false,
    "body_note": "Тело запроса в подпись НЕ входит. Подписываются только timestamp и login. / The request body is NOT part of the signature."
  },
  "credentials_note": "demo_login / demo_password — публичные seed-доступы песочницы; после локальной сверки векторов те же значения работают в живых запросах.",
  "docs": "https://pay-sandbox.digitalspb.com/help#auth",
  "vectors": [
    {
      "id": "v3-json-valid",
      "valid": true,
      "protocol": "REST JSON v3",
      "login": "demo_login",
      "secret": "demo_password",
      "timestamp": "2026-02-10T17:56:37Z",
      "inner": "ab4ea3fdd8fecb8dd57f19e087fcab319eb344629b0e8502622fcf230d09a6ee",
      "outer_message": "demo_loginab4ea3fdd8fecb8dd57f19e087fcab319eb344629b0e8502622fcf230d09a6ee",
      "signature": "0c902262f3cfc2282b05a2ccf38f59cce766617badf15c458eb32547f0a0841e"
    },
    {
      "id": "v3-xml-valid",
      "valid": true,
      "protocol": "XML Merchant API v2.6",
      "note": "Тот же момент времени, что в v3-json-valid, но в формате XML-контура — подпись другая: timestamp подписывается как строка, в том виде, в котором уходит в запрос.",
      "login": "demo_login",
      "secret": "demo_password",
      "timestamp": "20260210175637",
      "inner": "6d722ac72da42f825167f61268f6dbdf0246c18f97889b71da844293367dc5f6",
      "outer_message": "demo_login6d722ac72da42f825167f61268f6dbdf0246c18f97889b71da844293367dc5f6",
      "signature": "2f412b1a3b08a2ae3a5a427753bff4fd2d719e911d1eaa057d2cfa9e598dc163"
    },
    {
      "id": "v3-body-not-signed",
      "valid": true,
      "protocol": "REST JSON v3",
      "note": "Контроль: те же login/secret/timestamp, что в v3-json-valid, но с телом запроса — подпись ОБЯЗАНА совпасть с v3-json-valid. Тело (включая кириллицу в description) на подпись не влияет. Если ваша реализация даёт здесь другое значение — вы подписываете тело; в этом API так не делается.",
      "login": "demo_login",
      "secret": "demo_password",
      "timestamp": "2026-02-10T17:56:37Z",
      "request_body": {
        "order": {
          "merchant_order_id": "ORD-42",
          "price": {
            "amount": "100.00",
            "currency": "RUB",
            "vat_included": true
          }
        },
        "description": "Оплата тарифа «Медиатека», абонент 7960XXXXXXX"
      },
      "signature": "0c902262f3cfc2282b05a2ccf38f59cce766617badf15c458eb32547f0a0841e"
    },
    {
      "id": "v4-json-create-invoice",
      "valid": true,
      "protocol": "REST JSON v3, SignatureVersion=4",
      "note": "v4 добавляет requestPayload — конкатенацию значимых полей операции (таблица §9 спеки). Поля берутся из тела как есть: merchant_order_id и price лежат внутри order. Идентификаторы счёта и контракта в других операциях берутся из пути URL, а не из тела.",
      "login": "demo_login",
      "secret": "demo_password",
      "timestamp": "2026-02-10T17:56:37Z",
      "endpoint": "POST /api/v3/projects/1001/invoices",
      "body": {
        "client_ip": "10.20.30.40",
        "customer": {
          "type": "mobile_phone",
          "msisdn": "79600000000"
        },
        "order": {
          "merchant_order_id": "order_123",
          "price": {
            "amount": "25.00",
            "currency": "RUB",
            "vat_included": true
          }
        }
      },
      "request_payload": "order_1237960000000025.00RUB10.20.30.40",
      "inner": "ab4ea3fdd8fecb8dd57f19e087fcab319eb344629b0e8502622fcf230d09a6ee",
      "outer_message": "order_1237960000000025.00RUB10.20.30.40demo_loginab4ea3fdd8fecb8dd57f19e087fcab319eb344629b0e8502622fcf230d09a6ee",
      "signature": "7ad9a36a3e9004e0bb3fced3b89c7edffa50369dd00aaf54f06cfb814b79dea9"
    },
    {
      "id": "v3-negative-single-pass",
      "valid": false,
      "protocol": "REST JSON v3",
      "why_invalid": "Один проход HMAC по login + timestamp вместо двух проходов через inner — самая частая неверная реализация. Сервер ответит 401 AUTH_INVALID_SIGNATURE. Реализация, принимающая этот вектор, неверна.",
      "login": "demo_login",
      "secret": "demo_password",
      "timestamp": "2026-02-10T17:56:37Z",
      "signature": "c5261f39339d743804df72d2141653e8f0dc718cd5033f4e04899fed45be067b"
    },
    {
      "id": "v3-negative-raw-inner",
      "valid": false,
      "protocol": "REST JSON v3",
      "why_invalid": "inner передан во второй HMAC сырыми байтами (bytes.fromhex) вместо hex-строки. Значение выглядит правдоподобно, но сервер его отвергнет.",
      "login": "demo_login",
      "secret": "demo_password",
      "timestamp": "2026-02-10T17:56:37Z",
      "signature": "a615c666319b0c5d42a300354accd7f2bae4de418eb5853d438cf370db1e00c7"
    }
  ]
}
