paytocrypt

Руководство по интеграции

Общее описание

Данный документ описывает общие принципы работы с командами API платежной системы paytocrypt, а именно:

  • формат команд;
  • работу с ошибками;
  • аутентификация.

Формат команд

API системы paytocrypt построено с использованием REST-API, где тип операции определяется по пути и методу HTTP-запроса, а данные операций содержатся в JSON-формате в теле HTTP-запросов и ответов. Аутентификация осуществляется с использованием соответствующих токенов, передаваемых в HTTP-заголовках (см. ниже).

Описание команд самого REST-API для проведения транзакций приводится в отдельном документе.

Обработка ошибок

В случае ошибки сервер возвращает HTTP-статус с кодом большим или равным 400. В случае получения такого кода необходимо проверить тело отклика. Стандартные ответы ошибок сервера имеют формат JSON-документов со строковыми полями:

  • code
  • message
  • details (может отсутствовать)
  • data (может отсутствовать)

Пример:

{
    "code":"login_credentials_required",
    "message":"Credentials hash must be provided in request",
    "details":"hash not found in header",
    "data":{}
}

Поле code содержит код ошибки. Поле message содержит текстовое описание ошибки, которое можно показывать пользователю. Поле details может содержать дополнительные сведения об ошибки. Поле data может содержать дополнительные данные об ошибке в виде JSON-объекта.

При получении от сервера ответа с HTTP-статусом >=400 необходимо выполнить следующие шаги.

  1. Попробовать десериализовать JSON-тело ответа в соответствии с форматом, приведенным выше. Если десериализация не удается, то это означает, что ошибка выявлена либо на стороне клиента, либо на уровне сети, либо сервер недоступен. В таком случае необходимо отображать пользователю ошибку как есть из тела отклика.

  2. Проверить code ошибки в обработчиках схем аутентификации в соответствии с правилами, приведенными ниже для каждой схемы. Для части кодов ошибок схем аутентификации необходимо выполнить дополнительные действия со стороны клиента, после чего повторить запрос с теми же данными, но заполненными или обновленными полями заголовка запроса.

  3. Во всех остальных случаях обрабатывать ошибку в рамках текущей операции. В большинстве случаев это означает, что необходимо просто показать пользователю сообщение об ошибке из поля message. Но, возможно, какие-то операции требуют специальной дополнительной обработки ошибки.

Схема аутентификации

Аутентификация работает с использованием специальных дополнительных полей в заголовках запросов HTTP.

Аутентификация с использованием токенов

Получение токенов аутентификации

Получить токены можно, выполнив авторизацию с помощью логина и пароля. Авторизация выполняется в два запроса:

  1. Клиент производит запрос POST /auth/login с HTTP-заголовком X-Auth-Login, в котором указывает логин пользователя. Пример:

    curl -v -i https://example.com/api/v1.0.0/auth/login \
      -X POST \
      -H "Content-Type: application/json" \
      -H "X-Auth-Login: user1@example.com"
    

    Сервер в ответ вернет ошибку login_credentials_required, при этом ответ содержит HTTP-заголовок X-Auth-Login-Salt, в котором записана соль для вычисления хэша от пароля. Пример:

    Header:
    X-Auth-Login-Salt: AVast5zVNKoVJoPQ
    
    Data:
    {"code":"login_credentials_required","message":"Credentials hash must be provided in request","details":""}
    
  2. Клиент должен вычислить хэш по формуле hash=base64(sha256(salt+password)), где salt и password объединяются в одну строку без разделителей, после чего строка подается на вход hash-функции.

    Пример на Golang:

    salt="AVast5zVNKoVJoPQ"; // значение salt в реальных условиях следует брать из заголовка X-Auth-Login-Salt
    password="12345678";
    str := salt+password
    hh := sha256.New()
    hh.Write([]byte(str))
    hashed := hh.Sum(nil)
    phash := base64.StdEncoding.EncodeToString(hashed)
    

    Пример на PHP:

    $salt="AVast5zVNKoVJoPQ"; // значение salt в реальных условиях следует брать из заголовка X-Auth-Login-Salt
    $password="12345678";
    $str = $salt.$password; // строка str равна "AVast5zVNKoVJoPQ12345678"
    $phash = base64_encode(hash('sha256', $str, true)); // USX0DFXfMu6bQLE26Mbdx/B+7G15lf+YID74+ZKtY5A=
    

    Затем на сервер отправляется повторный запрос, в котором установлены HTTP-заголовки X-Auth-Login и X-Auth-Login-Phash:

    curl -v -i https://example.com/api/v1.0.0/auth/login \
      -X POST \
      -H "Content-Type: application/json" \
      -H "X-Auth-Login: user1@example.com" \
      -H "X-Auth-Login-Phash: USX0DFXfMu6bQLE26Mbdx/B+7G15lf+YID74+ZKtY5A="
    

В случае успешного логина сервер вернет HTTP-статус 200. Ответ будет содержать токен доступа в HTTP-заголовке X-Auth-Access-Token и токен обновления в HTTP-заголовке X-Auth-Refresh-Token. Пример:

X-Auth-Access-Token: aFSaDr2gRD6FDCw4OfgOMWUBJpCjqt31u5EcK6njFxg77rsnFaCCwUibtqHCYhGWXOcM6+AGJaEa/Iw5yW33v2XHzzdBruUEnAMqIInPaEw s2RsOcpQPAK+l0FconDqrgy0YbWsuikck/giV54iUluLYNYZSslzreBemOiwM6oqP1JLWHH9aAtKYbnhX0J9ewVDbHXso2sJKI5SL6aEMcYjGDLD0k4pw3JSL/yRVd0qbcj9bZfkUjyHcgSD6KmMMrNX9RZzcXzbvhR5L66WdfCYuXxdnu72bKUqT7es/+8X2mX0=

X-Auth-Refresh-Token: CDxVrL3rN8IAqPKgK1Rx9eeqz76haZS1QNQZaK9thz9WB/ptqQpuTmkpxiwHzVcnchLr/MDcgjJqvYahz1pkg2TojKowoAfUYeCYgnBA1s5BEFdSKLIB/Uv31poQfU+8zF931Pi4Wl2FPqzs9B99rHUCRGcEfc5zV8Qvf0kAmtkZnNeXtLlvd5EvI2PbsJKq6aLwBMzo37xJSYbQUECjG6IY+RLV4Dej6MwSAmRcGU3xQ8n1w+hRQ1XO+QfEe2KenCb53TuGWcwMwoRN4ee6v3KJPsOtj5k6RE1bnDJKqeWH3SQ=

В случае неверного логина или пароля сервер вернет ошибку login_failed, которую надо отобразить пользователю и проверить правильность написания логина или пароля.

Обновление X-Auth-Access-Token

Для обновления токена доступа можно использовать команду POST /auth/refresh без необходимости повторной авторизации и получения токенов до тех пор, пока токен обновления не устареет. Пример:

curl -v -i https://example.com/api/v1.0.0/auth/refresh \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Auth-Refresh-Token: CDxVrL3rN8IAqPKgK1Rx9eeqz76haZS1QNQZaK9thz9WB/ptqQpuTmkpxiwHzVcnchLr/MDcgjJqvYahz1pkg2TojKowoAfUYeCYgnBA1s5BEFdSKLIB/Uv31poQfU+8zF931Pi4Wl2FPqzs9B99rHUCRGcEfc5zV8Qvf0kAmtkZnNeXtLlvd5EvI2PbsJKq6aLwBMzo37xJSYbQUECjG6IY+RLV4Dej6MwSAmRcGU3xQ8n1w+hRQ1XO+QfEe2KenCb53TuGWcwMwoRN4ee6v3KJPsOtj5k6RE1bnDJKqeWH3SQ="

Заголовки в ответе:

X-Auth-Access-Token: aFSaDr2gRD6FDCw4OfgOMWUBJpCjqt31u5EcK6njFxg77rsnFaCCwUibtqHCYhGWXOcM6+AGJaEa/Iw5yW33v2XHzzdBruUEnAMqIInPaEws2RsOcpQPAK+l0FconDqrgy0YbWsuikck/giV54iUluLYNYZSslzreBemOiwM6oqP1JLWHH9aAtKYbnhX0J9ewVDbHXso2sJKI5SL6aEMcYjGDLD0k4pw3JSL/yRVd0qbcj9bZfkUjyHcgSD6KmMMrNX9RZzcXzbvhR5L66WdfCYuXxdnu72bKUqT7es/+8X2mX0=

X-Auth-Refresh-Token: CDxVrL3rN8IAqPKgK1Rx9eeqz76haZS1QNQZaK9thz9WB/ptqQpuTmkpxiwHzVcnchLr/MDcgjJqvYahz1pkg2TojKowoAfUYeCYgnBA1s5BEFdSKLIB/Uv31poQfU+8zF931Pi4Wl2FPqzs9B99rHUCRGcEfc5zV8Qvf0kAmtkZnNeXtLlvd5EvI2PbsJKq6aLwBMzo37xJSYbQUECjG6IY+RLV4Dej6MwSAmRcGU3xQ8n1w+hRQ1XO+QfEe2KenCb53TuGWcwMwoRN4ee6v3KJPsOtj5k6RE1bnDJKqeWH3SQ=

При устаревании X-Auth-Refresh-Token, следует авторизоваться повторно и получить новую пару токенов. Срок жизни X-Auth-Access-Token составляет 24 часа, X-Auth-Refresh-Token - 30 дней.

Работа с токенами аутентификации

Каждый запрос через API к серверу аутентифицируется с использованием токена доступа в HTTP-заголовке X-Auth-Access-Token. То есть все запросы к серверу в рамках API должны включать заполненный заголовок X-Auth-Access-Token. Основные принципы использования токенов сводятся к следующему:

  • Первый раз клиент получает токен доступа в ответе от сервера в результате успешно пройденной процедуры авторизации.

  • Клиент должен отслеживать поле заголовка X-Auth-Access-Token во всех ответах с сервера. В случае наличия такого заголовка в ответе с сервера необходимо сохранить и использовать новый полученный токен доступа для всех последующих запросов к серверу.

  • Для длительного сохранения авторизации можно хранить токен обновления, полученный с сервера в HTTP-заголовке X-Auth-Refresh-Token в результате авторизации. Этот токен используется для регулярного обновления токена доступа командой POST /auth/refresh. Обновление токена необходимо производить при получении от сервера ошибки auth_token_expired на любой запрос, кроме самого запроса POST /auth/refresh. Если на сам запрос обновления POST /auth/refresh сервер вернул ошибку auth_token_expired, это означает, что токен обновления также устарел и необходимо заново логиниться в систему. Заголовок X-Auth-Refresh-Token необходимо проверять в каждом отклике от сервера, и если он там есть, то переписывать соответствующий токен в локальном хранилище и в дальнейшем использовать только новый токен обновления.

  • При ошибках auth_token_invalid или session_expired необходимо заново проводить процедуру авторизации.

Прекращение авторизации

Деавторизация происходит автоматически после устаревания токенов доступа и обновления.

Также можно выполнить запрос POST /auth/logout. Данный запрос должен содержать HTTP-заголовок X-Auth-Access-Token. Пример:

curl -v -i https://example.com/api/v1.0.0/auth/logout \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Auth-Access-Token: aFSaDr2gRD6FDCw4OfgOMWUBJpCjqt31u5EcK6njFxg77rsnFaCCwUibtqHCYhGWXOcM6+AGJaEa/Iw5yW33v2XHzzdBruUEnAMqIInPaEws2RsOcpQPAK+l0FconDqrgy0YbWsuikck/giV54iUluLYNYZSslzreBemOiwM6oqP1JLWHH9aAtKYbnhX0J9ewVDbHXso2sJKI5SL6aEMcYjGDLD0k4pw3JSL/yRVd0qbcj9bZfkUjyHcgSD6KmMMrNX9RZzcXzbvhR5L66WdfCYuXxdnu72bKUqT7es/+8X2mX0="

Цифровая подпись команд

Некоторые команды API могут дополнительного требовать использования цифровой подписи, алгоритм формирования которой описан в разделе Формирование подписи этого документа

Полученное в результате генерации подписи значение заносится в HTTP-заголовок X-Auth-Signature:

curl -v -i https://example.com/api/v1.0.0/creation-service/create-object \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Auth-Access-Token: aFSaDr2gRD6FDCw4OfgOMWUBJpCjqt31u5EcK6njFxg77rsnFaCCwUibtqHCYhGWXOcM6+AGJaEa/Iw5yW33v2XHzzdBruUEnAMqIInPaEws2RsOcpQPAK+l0FconDqrgy0YbWsuikck/giV54iUluLYNYZSslzreBemOiwM6oqP1JLWHH9aAtKYbnhX0J9ewVDbHXso2sJKI5SL6aEMcYjGDLD0k4pw3JSL/yRVd0qbcj9bZfkUjyHcgSD6KmMMrNX9RZzcXzbvhR5L66WdfCYuXxdnu72bKUqT7es/+8X2mX0=" \
  -H "X-Auth-Signature: A91jmqj28/8t3J4DSTwTvGDTh4JnJGK2kcI9f906e2JMF/iX7K3FKDj8MWCQLxrfZOgYbZNKgpj+vqColYsD/v2npoCNbGsBsUfo2/e3SFOg7Xku0uUrVGL+AWsA8zN4AFcfbYJ+ho5ZX8b+gHPmiJLsb2Ea3852gGTZbFVi7QuCPCbnAWld+O5sxKal/KFCIbo+Qb7hZHiGLc05ruvbo4zB8xnsWcEbw/TKRaST3p4M9qLY96afaS6pxT2Bujh8bekS0yFmlLTcd9Q14Mqu2jyziYgAhPq7F+K298Lod3hpsGbZGjclOdLY1avFOPzAOE4ztFCse0dGp51CdIqNZA==" \
  -d '{"field1":"value1","field2":100}'

Подготовка ключей

Для подписи запросов необходимо предварительно сгенерировать ключевую пару RSA и передать поставщику услуг публичный ключ от сгенерированной пары. Ключевая пара может быть сгенерирована с использованием команды openssl:

openssl genrsa -out private_key.pem 3072
openssl rsa -in private_key.pem -outform PEM -pubout -out public_key.pem

Формирование подписи

Подпись запроса к серверу вычисляется следующим образом:

  1. Вычисляется значение хэш-функции SHA-256 строки, полученой путем конкатенации содержимого HTTP-пакета (body), метода запроса и пути запроса в рамках API:
    str = body + method + path
    hash = SHA256(str)
    
  2. Полученное значение хэш-функции подписывается с использованием приватного RSA-ключа
  3. Результат кодируется в строку base64

Пример на Golang:

// Подготовим приватный ключ:
pemData := []byte(`-----BEGIN RSA PRIVATE KEY-----
....
-----END RSA PRIVATE KEY-----`)
pemBlock, _ := pem.Decode(pemData)
key, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
if err != nil {
    ... // обработка ошибки
}

/* 
Формируем строку для подписи, например, для запроса /payment/reverse:
{"order_id":"a701748f0851","order_date":"2022-04-10","transaction_id":"64510775012565440000","reason":"Some reason","reference_id":"r-124997"}POST/payment/reverse
*/
body := '{"order_id":"a701748f0851","order_date":"2022-04-10","transaction_id":"64510775012565440000","reason":"Some reason","reference_id":"r-124997"}'
method := "POST"
path := "/payment/reverse"
str := body + method + path 

// Cчитаем хэш
hh := sha256.New()
hh.Write([]byte(str))
hash := hh.Sum(nil)

// Подписываем полученное значение с использованием приватного RSA-ключа
binSig, err := rsa.SignPKCS1v15(nil, key, crypto.SHA256, hash)
if err !=nil {
    ... // обработка ошибки
}

// Кодируем полученные бинарные данные в строку base64. Это результирующее значение:
sig := base64.RawStdEncoding.WithPadding(base64.StdPadding).EncodeToString(binSig)

В некоторых языках программирования стандартные функции цифровой подписи уже объединяют этап вычисления хэш-функции с вычислением цифровой подписи. Например, в PHP функция openssl_sign() выполняет предварительное вычисление хэша, а затем подписывает значение цифровой подписью.

Пример на PHP:

// Подготовка ключа и контрольной строки:
$key = "-----BEGIN RSA PRIVATE KEY-----
....
-----END RSA PRIVATE KEY-----";
$data = '{"order_id":"a701748f0851","order_date":"2022-04-10","transaction_id":"64510775012565440000","reason":"Some reason","reference_id":"r-124997"}'
      ."POST"
      ."/payment/reverse";
      
// Формирование подписи и получение результирующей строки:
    if (openssl_sign($data, $binSig, $key, OPENSSL_ALGO_SHA256)) {
        $sig = base64_encode($binSig);
    }
    else {
    ... // обработка ошибки формирования подписи
    }

Во всех случаях результат вычисления цифровой подписи должен совпадать с результатом выполнения цепочки команд openssl, где data.txt - файл с содержимым контрольной строки:

openssl dgst -sign key.pem -keyform PEM -sha256 -binary data.txt | openssl enc -base64