Обработка платёжных уведомлений

Уведомления посылаются сервером платёжной системы на Адрес обратного вызова, указанный в настройках приложения, методом POST в кодировке UTF-8.

Чтобы исключить возможность подделки, уведомление подписывается секретным ключом, известным только владельцу приложения и платежной системе, (подробнее в разделе Проверка подписи).

Разработчик должен реализовать обработку уведомлений и вернуть результат обработки в случае успеха или описание ошибки в случае неуспеха (см. подробнее в разделе Формат ответа в случае ошибки). Ответ должен быть отправлен в течение 10 секунд, иначе соединение будет разорвано, а попытка отправки уведомления будет предпринята ещё раз через некоторое время.

Важно! Ответ должен быть в формате JSON в кодировке UTF-8.

Структура уведомления

Набор полей зависит от типа уведомления. Однако уведомление любого типа всегда содержит следующие поля:

notification_type

string

Тип уведомления. Возможные значения:

  • get_item — получение информации о товаре;
  • order_status_change — изменение статуса заказа;
  • get_subscription — получение информации о подписке;
  • subscription_status_change — изменение статуса подписки.

app_id

integer

Идентификатор приложения.

user_id

integer

Идентификатор пользователя, сделавшего заказ.

receiver_id

integer

Идентификатор получателя заказа (в данный момент совпадает с user_id, но в будущем может отличаться).

order_id/subscription_id

integer/integer

Идентификатор заказа/подписки.

version

string

Параметр передаётся начиная с версии платежного API 5.132. Используемая версия платёжного API.

sig

string

Подпись уведомления (подробнее в разделе Проверка подписи).

Версионирование уведомлений

В настройках приложения, в разделе Платежи доступно переключение версий платёжных уведомлений. Начиная с версии 5.132 становятся доступны новые типы уведомлений — об отменах платежей, а также добавляется параметр version, опивающий версию используемого API.

Проверка подписи

Параметр sig равен md5 от конкатенации пар имя параметров=значение параметра, расположенных в порядке возрастания имени параметра (по алфавиту) и секретной подписи api_secret, указанной в настройках вашего приложения.

При несовпадении подписей необходимо выдать в ответе ошибку 10, «Несовпадение вычисленной и переданной подписи» (см. Формат ответа в случае ошибки).

Пример формирования подписи с секретным ключом W7kVvxVxZ4:

sig = md5("name1=value1name2=value2W7kVvxVxZ4")

Формат ответа в случае ошибки

Если при обработке уведомления произошла ошибка, необходимо отправить ответ в следующем формате:

JSON{ "error": { "error_code": код ошибки, "error_msg": описание ошибки, "critical": критичность ошибки } }

Поля объекта error

error_code

integer

Числовой код ошибки (см. ниже).

error_msg

string

Описание ошибки в текстовом виде для чтения человеком (обязательно для error_code >= 100).

critical

boolean

Критичность ошибки. Возможные значения:

  • true, если повторение уведомления с такими же параметрами приведёт к такой же ошибке (например, указанного товара не существует). Уведомление не будет отправляться повторно, пользователь получит ошибку.
  • false, если ошибка временная, и уведомление может быть обработано позже (например, временная ошибка записи в базу данных). Уведомление будет отправлено через некоторое время, пользователь будет ждать ответа.

Числовые коды ошибок

Код ошибкиКритичностьОписание ошибки в текстовом виде
1true/falseОбщая ошибка.
2falseВременная ошибка базы данных.
10trueНесовпадение вычисленной и переданной подписи.
11trueПараметры запроса не соответствуют спецификации; в запросе нет необходимых полей; другие ошибки целостности запроса.
20trueТовара не существует.
21trueТовара нет в наличии.
22trueПользователя не существует.
100-999Ошибки с номерами 100-999 задаются приложением, при возврате таких ошибок обязательно должно присутствовать текстовое описание ошибки.

Пример обработчика уведомлений на языке PHP

PHP<?php header("Content-Type: application/json; encoding=utf-8"); $secret_key = 'hiUl8U4F9q3BcbAl28va'; // Защищённый ключ приложения $input = $_POST; // Проверка подписи $sig = $input['sig']; unset($input['sig']); ksort($input); $str = ''; foreach ($input as $k => $v) { $str .= $k.'='.$v; } if ($sig != md5($str.$secret_key)) { $response['error'] = array( 'error_code' => 10, 'error_msg' => 'Несовпадение вычисленной и переданной подписи запроса.', 'critical' => true ); } else { // Подпись правильная switch ($input['notification_type']) { case 'get_item': // Получение информации о товаре $item = $input['item']; // наименование товара if ($item == 'item1') { $response['response'] = array( 'item_id' => 25, 'title' => '300 золотых монет', 'photo_url' => 'http://somesite/images/coin.jpg', 'price' => 5 ); } elseif ($item == 'item2') { $response['response'] = array( 'item_id' => 27, 'title' => '500 золотых монет', 'photo_url' => 'http://somesite/images/coin.jpg', 'price' => 10 ); } else { $response['error'] = array( 'error_code' => 20, 'error_msg' => 'Товара не существует.', 'critical' => true ); } break; case 'get_item_test': // Получение информации о товаре в тестовом режиме $item = $input['item']; if ($item == 'item1') { $response['response'] = array( 'item_id' => 125, 'title' => '300 золотых монет (тестовый режим)', 'photo_url' => 'http://somesite/images/coin.jpg', 'price' => 5 ); } elseif ($item == 'item2') { $response['response'] = array( 'item_id' => 127, 'title' => '500 золотых монет (тестовый режим)', 'photo_url' => 'http://somesite/images/coin.jpg', 'price' => 10 ); } else { $response['error'] = array( 'error_code' => 20, 'error_msg' => 'Товара не существует.', 'critical' => true ); } break; case 'order_status_change': // Изменение статуса заказа if ($input['status'] == 'chargeable') { $order_id = intval($input['order_id']); // Код проверки товара, включая его стоимость $app_order_id = 1; // Получающийся у вас идентификатор заказа. $response['response'] = array( 'order_id' => $order_id, 'app_order_id' => $app_order_id, ); } else { $response['error'] = array( 'error_code' => 100, 'error_msg' => 'Передано непонятно что вместо chargeable.', 'critical' => true ); } break; case 'order_status_change_test': // Изменение статуса заказа в тестовом режиме if ($input['status'] == 'chargeable') { $order_id = intval($input['order_id']); $app_order_id = 1; // Тут фактического заказа может не быть - тестовый режим. $response['response'] = array( 'order_id' => $order_id, 'app_order_id' => $app_order_id, ); } else { $response['error'] = array( 'error_code' => 100, 'error_msg' => 'Передано непонятно что вместо chargeable.', 'critical' => true ); } break; } } echo json_encode($response); ?>