Аутентификация и подпись
Регистрация в системе
Регистрация в системе выполняется через личный кабинет мерчанта. Доступ в личный кабинет предоставляет специалист HighHelp.
После входа в личный кабинет необходимо сгенерировать пару RSA-ключей (публичный и приватный) для кассы.
Порядок действий:
-
Создайте кассу во вкладке Кассы.
-
Обратитесь к прикрепленному специалисту для настройки кассы и проведения верификации.
-
После настройки и верификации кассы перейдите во вкладку API.
-
Найдите созданную кассу и нажмите кнопку Сгенерировать ключ.
-
В открывшемся окне будут указаны:
-
название кассы;
-
URL продукта.
-
-
Нажмите кнопку Сгенерировать ключ.
-
Скопируйте и сохраните значения:
-
UUID— идентификатор кассы. Используется при взаимодействии с API; -
Private key— приватный RSA-ключ в формате PEM. Используется для формирования цифровой подписи.Приватный ключ генерируется на стороне браузера и не сохраняется на стороне HighHelp. На стороне сервиса сохраняется только публичный ключ кассы. Публичный ключ используется для проверки подписи запросов.
-
-
Передайте значения
UUIDиPrivate keyв команду разработки или DevOps и обеспечьте их защищенное хранение.
Аутентификация в API
Для аутентификации запросов API используются следующие HTTP-заголовки:
-
x-access-timestamp -
x-access-merchant-id -
x-access-signature -
x-access-token
Заголовок x-access-timestamp
x-access-timestamp содержит время формирования запроса в формате Unix timestamp (количество секунд с 01.01.1970 00:00:00 UTC), указанное строкой.
Пример:
x-access-timestamp: 1716299720
Заголовок x-access-merchant-id
x-access-merchant-id содержит идентификатор кассы. Используется значение UUID, полученное при генерации ключей для кассы.
В примере кода идентификатор передается через переменную project_id.
Пример:
x-access-merchant-id: 57aff4db-b45d-42bf-bc5f-b7a499a01782
Заголовок x-access-token
x-access-token содержит публичный ключ кассы, закодированный в формате Base64Url. Публичный ключ получается из приватного RSA-ключа, экспортируется в бинарном виде и кодируется Base64Url.
Пример расчета значения заголовка x-access-token:
public_key = private_key.public_key().export_key()
api_key = base64.urlsafe_b64encode(public_key).decode("utf-8")
# api_key используется как значение x-access-token
Заголовок x-access-signature
x-access-signature содержит цифровую подпись запроса.
Подпись формируется по следующему алгоритму:
-
Нормализовать тело запроса в строку
joined_resultс помощью функцииnormalize_message. -
Закодировать строку
joined_resultв Base64Url. -
Конкатенировать полученную Base64Url-строку и значение
timestampв виде строки. -
Вычислить хеш полученной строки по алгоритму SHA-256.
-
Подписать хеш приватным RSA-ключом по схеме RSA-SHA256.
-
Закодировать подпись в Base64Url.
-
Передать полученное значение в заголовке
x-access-signature.
Формат строки сообщения:
message = base64url(normalized_payload) + str(timestamp)
signature = RSA-SHA256(message)
x-access-signature = base64url(signature)
Где:
-
normalized_payload— строковое представление тела запроса после нормализации; -
timestamp— значение заголовкаx-access-timestamp.
Если тело запроса отсутствует, используется пустой объект {}.
Схема формирования подписи запроса
flowchart TD
%% Входные данные
A["JSON-тело запроса (payload)"] --> B["Нормализовать payload\n(normalize_message)"]
B --> C["Собрать строку параметров"]
C --> D["Закодировать строку в Base64Url"]
%% Добавление метки времени
D --> E["Соединить с timestamp"]
T["Метка времени (timestamp)"] --> E
E --> F["Вычислить SHA-256 от строки"]
F --> G["Подписать результат RSA-ключом"]
G --> H["Заголовок x-access-signature"]
%% Формирование токена
P["Публичный ключ кассы"] --> Q["Закодировать ключ в Base64Url"]
Q --> R["Заголовок x-access-token"]
%% Общий результат
H --> Z["HTTP-запрос с заголовками"]
R --> Z
Пример запроса с подписью (Python3)
import base64
import json
import time
import requests
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature.pkcs1_15 import PKCS115_SigScheme
url = "https://api.hh-processing.com/api/v1/payment/p2p/payin"
# Идентификатор кассы. Получен при создании кассы и генерации ключей для нее
project_id = "57aff4db-b45d-42bf-bc5f-b7a499a01782"
# Путь до файла с приватным ключом, который вы получили в личном кабинете
private_key_path = "./private.pem"
payload = {
"general": {
"project_id": project_id
}
}
def parse_json(prefix, _obj, _result):
if isinstance(_obj, dict):
for key, value in _obj.items():
new_prefix = f"{prefix}:{key}" if prefix else key
parse_json(new_prefix, value, _result)
elif isinstance(_obj, list):
for index, item in enumerate(_obj):
new_prefix = f"{prefix}:{index}"
parse_json(new_prefix, item, _result)
else:
_result.append(f"{prefix}:{_obj or 'None'}")
def normalize_message(_payload):
_result = []
parse_json("", _payload, _result)
_result.sort()
_joined_result = ";".join(_result)
return _joined_result
with open(private_key_path, "rb") as f:
private_key = RSA.import_key(f.read())
public_key = private_key.public_key().export_key()
api_key = base64.urlsafe_b64encode(public_key).decode("utf-8")
timestamp = int(time.time())
if payload:
dumped = json.dumps(payload, separators=(",", ":"))
else:
dumped = "{}"
joined_result = normalize_message(payload)
message = "{}{}".format(
base64.urlsafe_b64encode(joined_result.encode()).decode("utf-8"),
str(timestamp),
).encode("utf-8")
signer = PKCS115_SigScheme(private_key)
signature = signer.sign(SHA256.new(message))
base64_sign = base64.urlsafe_b64encode(signature).decode("ascii")
headers = {
"content-type": "application/json",
"x-access-token": api_key,
"x-access-signature": base64_sign,
"x-access-merchant-id": project_id,
"x-access-timestamp": str(timestamp),
}
if payload:
response = requests.post(
url,
headers=headers,
data=dumped,
)
else:
response = requests.get(
url,
headers=headers,
)
print(response.status_code)