Процесс интеграции через SDK
Ознакомьтесь с документацией для API P2P-счетов.
Шаг 1. Подготовка среды разработки
-
Выберите SDK для вашего языка программирования и перейдите в репозиторий на GitHub:
//Установка с помощью npm
$ npm install @qiwi/bill-payments-node-js-sdk --save
//Установка с помощью composer
$ composer require qiwi/bill-payments-php-sdk
//Установка с помощью maven
<dependency>
<groupId>com.qiwi</groupId>
<artifactId>bill-payments-java-sdk</artifactId>
<version>1.5.0</version>
</dependency>
//Установка с помощью Nuget
nuget install Qiwi.BillPayments
- Ознакомьтесь с файлом README.md
-
Установите SDK >
Обратите внимание, что в зависимости от выбранного вами SDK потребуется установка Composer, Apache Maven или NuGet.
Шаг 2. Создание секретного ключа
Выпуск новых ключей прекращён. Простите за доставленные неудобства.
-
Перейдите на вкладку API.
-
Нажмите на кнопку Настроить и придумайте название, по которому потом сможете найти нужный API-ключ.
Рекомендуем подключить уведомления об оплате, отметив чекбокс Использовать эту пару ключей для серверных уведомлений об изменении статусов счетов. После настройки серверных уведомлений вы сможете узнавать, когда выставленные счета оплачены, и автоматически реагировать на оплату счетов (пополнять баланс, отгружать товар, давать доступ к контенту). Подробнее об уведомлениях см. в документации.
В поле URL сервера для уведомлений укажите адрес вашего сервера для обработки уведомлений об оплате.
-
Нажмите Создать и получите ключи авторизации.
-
Скопируйте себе секретный ключ (в оранжевом блоке), нажав на ссылку Скопировать в буфер. Рекомендуем не закрывать данное окно и не нажимать на кнопку Дальше, пока не настроите авторизацию.
Шаг 3. Авторизация
const QiwiBillPaymentsAPI = require('@qiwi/bill-payments-node-js-sdk');
const SECRET_KEY = 'eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************=';
const qiwiApi = new QiwiBillPaymentsAPI(SECRET_KEY);
<?php
const SECRET_KEY = 'eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************=';
$billPayments = new Qiwi\Api\BillPayments(SECRET_KEY);
?>
String secretKey = "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************=";
BillPaymentClient client = BillPaymentClientFactory.createDefault(secretKey);
var client = BillPaymentClientFactory.Create(
secretKey: "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************="
);
В разрабатываемом коде заведите константу SECRET_KEY и присвойте ей значение секретного ключа, вставив его из буфера обмена комбинацией клавиш Ctrl+V.
Проверьте, что ключ скопирован полностью: в конце должен быть знак "равно" =
.
См. также примеры авторизации.
Если возвращается ошибка авторизации HTTP/1.1 401 Unauthorized
, это значит что-то не так с секретным ключом. Выпустите новый ключ и скопируйте его полностью: в конце должен быть знак "равно" =
.
Шаг 4. Выставление счета
Реализуйте вызов метода createBill
.
Пример генерации
billId
<?php
/** @var \Qiwi\Api\BillPayments $billPayments */
$billId = $billPayments->generateId();
//e9b47ee9-b2f9-4b45-9438-52370670e2a6
?>
const billId = qiwiApi.generateId();
//e9b47ee9-b2f9-4b45-9438-52370670e2a6
Примеры генерации
expirationDateTime
. Входной параметр - сколько дней счёт будет доступен, по умолчанию 45 дней. Метод возвращает строку в формате ISO 8601 UTC±0:00
<?php
//now: 2021-01-28T17:16:58.033Z
/** @var \Qiwi\Api\BillPayments $billPayments */
$lifetime = $billPayments->getLifetimeByDay(1);
//2021-01-29T17:16:58.033Z
?>
<?php
//now: 2021-01-28T17:16:58.033Z
/** @var \Qiwi\Api\BillPayments $billPayments */
$lifetime = $billPayments->getLifetimeByDay(0.5);
//2021-01-29T05:16:58.033Z
?>
//now: 2021-01-28T17:16:58.033Z
const lifetime = qiwiApi.getLifetimeByDay(1);
//2021-01-29T17:16:58.033Z
//now: 2021-01-28T17:16:58.033Z
const lifetime = qiwiApi.getLifetimeByDay(0.5);
//2021-01-29T05:16:58.033Z
Запрос создания счета
const billId = 'cc961e8d-d4d6-4f02-b737-2297e51fb48e';
const fields = {
amount: 1.00,
currency: 'RUB',
comment: 'Hello world',
expirationDateTime: '2020-08-28T19:44:07',
email: 'example@mail.org',
account : 'client4563',
customFields : {themeCode: 'кодСтиля'}
};
qiwiApi.createBill( billId, fields ).then( data => {
//do with data
});
<?php
$billId = 'cc961e8d-d4d6-4f02-b737-2297e51fb48e';
$customFields = ['themeCode' => 'кодСтиля'];
$fields = [
'amount' => 1.00,
'currency' => 'RUB',
'comment' => 'Hello world',
'expirationDateTime' => '2020-08-28T19:44:07+03:00',
'email' => 'example@mail.org',
'account' => 'client4563',
'customFields' => $customFields,
];
/** @var \Qiwi\Api\BillPayments $billPayments */
$response = $billPayments->createBill($billId, $fields);
print_r($response);
?>
CreateBillInfo billInfo = new CreateBillInfo(
UUID.randomUUID().toString(),
new MoneyAmount(
BigDecimal.valueOf(199.90),
Currency.getInstance("RUB")
),
"Hello world",
ZonedDateTime.now().plusDays(45),
new Customer(
"example@mail.org",
UUID.randomUUID().toString(),
"79123456789"
),
""
);
BillResponse response = client.createBill(billInfo);
client.CreateBill(
info: new CreateBillInfo
{
BillId = Guid.NewGuid().ToString(),
Amount = new MoneyAmount
{
ValueDecimal = 199.9m,
CurrencyEnum = CurrencyEnum.Rub
},
Comment = "Hello world",
ExpirationDateTime = DateTime.Now.AddDays(45),
Customer = new Customer
{
Email = "example@mail.org",
Account = Guid.NewGuid().ToString(),
Phone = "79123456789"
},
CustomFields: new CustomFields
{
ThemeCode = "Ivan-VbRVlW-YGL"
}
},
);
curl --location \
--request PUT \
'https://api.qiwi.com/partner/bill/v1/bills/cc961e8d-d4d6-4f02-b737-2297e51fb48e' \
--header 'content-type: application/json' \
--header 'accept: application/json' \
--header 'Authorization: Bearer <SECRET_KEY>' \
--data-raw '{
"amount": {
"currency": "RUB",
"value": "1.00"
},
"comment": "Text comment",
"expirationDateTime": "2025-12-10T09:02:00+03:00",
"customer": {
"phone": "78710009999",
"email": "test@tester.com",
"account": "454678"
},
"customFields" : {
"paySourcesFilter":"qw",
"themeCode": "Yvan-YKaSh",
"yourParam1": "64728940",
"yourParam2": "order 678"
}
}'
Обязательные параметры:
-
_billId – сгенерированный вами идентификатор счета. Он должен быть уникальным и генерироваться на вашей стороне любым способом. Идентификатором может быть любая уникальная последовательность букв или цифр. Также разрешено использование символа подчеркивания (
_
) и дефиса (-
).В некоторых SDK предусмотрен вспомогательный метод
generateId
, позволяющий генерировать уникальные идентификаторы. - amount – информация о сумме перевода:
- amount.value – сумма счёта;
- amount.currency – валюта счёта:
- RUB;
- KZT;
-
expirationDateTime – срок оплаты счёта в формате
ГГГГ-ММ-ДДTчч:мм:сс±чч:мм
, например,2020-11-05T11:27:41+03:00
.В некоторых SDK предусмотрен вспомогательный метод
getLifetimeByDay
, позволяющий генерировать дату.
Рекомендуем использовать дополнительные параметры:
- comment – комментарий к платежу
- customFields.paySourcesFilter – настроить способы оплаты счёта. При открытии формы будут отображаться только указанные способы перевода (один или несколько), если они доступны. Возможные значения: * qw – QIWI Кошелек; * card – банковская карта.
- customFields.themeCode – применить настроенную персонализацию платежной формы. Значение themeCode вы можете найти в личном кабинете в разделе Форма приема переводов в сером блоке – см. скриншот. Обратите внимание, что значение themeCode индивидуально для разных кошельков.
Необязательные параметры для выставления счета:
- customer – информация о пользователе с вашего сайта, обычно используется если на вашем сайте есть профиль пользователя:
- customer.phone – номер телефона пользователя в международном формате;
- customer.email – e-mail пользователя;
- customer.account – идентификатор пользователя в вашей системе;
- customFields – дополнительные данные, необходимые вашей системе, передаются на ваше усмотрение, например:
- customFields.yourParam1 - любое значение, которое вы хотите получать в уведомлении или при запросе информации о счёте.
Ответ
{
"siteId": "270305",
"billId": "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
"amount": {
"currency": "RUB",
"value": "1.00"
},
"status": {
"value": "WAITING",
"changedDateTime": "2020-07-28T19:44:07.855+03:00"
},
"comment": "Мой комментарий",
"customFields": {
"themeCode": "кодСтиля",
},
"creationDateTime": "2020-07-28T19:44:07.855+03:00",
"expirationDateTime": "2020-08-28T19:44:07.855+03:00",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=bb773791-9bd9-42c1-b8fc-3358cd108422"
}
Array
(
[siteId] => 270305
[billId] => cc961e8d-d4d6-4f02-b737-2297e51fb48e
[amount] => Array
(
[currency] => RUB
[value] => 1.00
)
[status] => Array
(
[value] => WAITING
[changedDateTime] => 2020-07-28T19:44:38.855+03:00
)
[comment] => Hello world
[customFields] => Array
(
[themeCode] => кодТемы
)
[creationDateTime] => 2020-07-28T19:44:07.855+03:00
[expirationDateTime] => 2020-08-28T19:44:07.855+03:00
[payUrl] => https://oplata.qiwi.com/form/?invoice_uid=bb773791-9bd9-42c1-b8fc-3358cd108422
)
{
"siteId": "270304",
"billId": "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
"amount": {
"value": "199.90",
"currency": "RUB"
},
"status": {
"value": "WAITING",
"changedDateTime": "2020-08-28T19:44:51.407Z"
},
"comment": "Мой комментарий",
"customer": {
"email": "example@mail.org",
"account": "040c3bb8-b207-4ecc-9ff9-90168d3bc34f",
"phone": "79123456789"
},
"creationDateTime": "2020-07-28T19:49:07.407Z",
"expirationDateTime": "2020-09-10T19:49:07.407Z",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=c77a9051-1467-416b-991e-c25f06c61168",
"customFields": {
"apiClient": "java_sdk",
"apiClientVersion": "1.0.0"
}
}
new BillResponse
{
SiteId = "270304",
BillId = "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
Amount = new MoneyAmount
{
ValueString = "199.90",
CurrencyString = "RUB"
},
Status = new ResponseStatus
{
ValueString: "WAITING",
ChangedDateTime: BillPaymentsUtils.ParseDate("2020-07-28T19:44:07+03:00")
},
Comment = "Hello world",
Customer = new Customer
{
Email = "example@mail.org",
Account = "040c3bb8-b207-4ecc-9ff9-90168d3bc34f",
Phone = "79123456789"
},
CreationDateTime = BillPaymentsUtils.ParseDate("2020-07-28T19:44:07+03:00"),
ExpirationDateTime = BillPaymentsUtils.ParseDate("2020-09-10T19:44:07+03:00"),
PayUrl = new Uri("https://oplata.qiwi.com/form/?invoice_uid=c77a9051-1467-416b-991e-c25f06c61168"),
CustomFields = new CustomFields
{
ApiClient = "dotnet_sdk",
ApiClientVersion = "0.1.0",
ThemeCode = "Ivan-VbRVlW-YGL"
}
};
{
"siteId": "9hh4jb-00",
"billId": "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
"amount": {
"currency": "RUB",
"value": "1.00"
},
"status": {
"value": "WAITING",
"changedDateTime": "2021-01-18T14:22:56.672+03:00"
},
"customer": {
"phone": "78710009999",
"email": "test@tester.com",
"account": "454678"
},
"customFields": {
"paySourcesFilter": "qw",
"themeCode": "Yvan-YKaSh",
"yourParam1": "64728940",
"yourParam2": "order 678"
},
"comment": "Text comment",
"creationDateTime": "2021-01-18T14:22:56.672+03:00",
"expirationDateTime": "2025-12-10T09:02:00+03:00",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=aa0fa2bb-5452-47ca-9190-cd9c1a73718f"
}
В ответе на запрос выставления счета возвращаются следующие поля:
- siteId – ваш идентификатор в системе p2p QIWI;
- billId – идентификатор счёта, указанный при его создании;
- amount – информация о сумме перевода:
- amount.value – сумма счёта;
- amount.currency – валюта счёта, RUB или KZT;
- status – данные о статусе счёта:
- status.value – текущий статус счёта;
- status.changedDateTime – дата обновления статуса.
Формат даты:ГГГГ-ММ-ДДTчч:мм:сс±чч:мм
- customer – информация о пользователе с вашего сайта, обычно используется если на вашем сайте есть профиль пользователя:
- customer.phone – номер телефона пользователя в международном формате;
- customer.email – e-mail пользователя;
- customer.account – идентификатор пользователя в вашей системе;
- customFields – объект строковых дополнительных параметров:
- customFields.paySourcesFilter – доступные способы оплаты счёта:
- qw – QIWI Кошелек;
- card – банковская карта;
- customFields.paySourcesFilter – доступные способы оплаты счёта:
- customFields.themeCode – код темы платежной формы;
- comment – комментарий к платежу;
- creationDateTime – системная дата создания счета.
Формат даты:ГГГГ-ММ-ДДTчч:мм:сс±чч:мм
; - expirationDateTime – срок действия созданной формы для перевода.
Формат даты:ГГГГ-ММ-ДДTчч:мм:сс±чч:мм
; - payUrl – ссылка на форму оплаты счета.
Если на запрос создания счёта вернулась ошибка — проверьте формат переданных в запрос параметров.
Для тестирования и отладки сервиса рекомендуем выставлять и оплачивать счета суммой 1 рубль.
См. также документацию с примерами выставления счетов.
Шаг 5. Установка дополнительных параметров к ссылке на счёт
https://oplata.qiwi.com/form/?invoice_uid=aa0fa2bb-5452-47ca-9190-cd9c1a73718f&paySource=qw
При выставлении счета через API в ответе присутствует поле payUrl, содержащее ссылку на форму. К данной ссылке можно добавить следующий параметр:
-
paySource – установить рекомендуемый способ оплаты, при открытии формы сразу будет выбран указанный способ перевода.
Возможные значения: * qw - QIWI Кошелек; * card - банковская карта; * mobile - платеж со счета мобильного телефона.
Шаг 6. Редирект пользователя на платежную форму
Пример получения URL оплаты по счету
<?php
/** @var array $bill */
/** @var \Qiwi\Api\BillPayments $billPayments */
$payUrl = $billPayments->getPayUrl($bill, 'http://test.ru/');
// https://oplata.qiwi.com/form/?invoice_uid=d875277b-6f0f-445d-8a83-f62c7c07be77
?>
Реализуйте на вашем сайте перенаправление пользователя на платежную форму по ссылке, полученной в поле payUrl
ответа на запрос выставления счёта или по ссылке с дополнительными параметрами, сформированной на шаге 5.
Добавьте реферальные ссылки для платежей с сайта. Полная ссылка подтвердит его реальность и позволит избежать проблем с блокировкой кошелька.
Пример передачи реферальной ссылки
<?php header("Referrer-Policy: no-referrer-when-downgrade"); ?>
Для того, чтобы в новых версиях браузеров передавался полный referer при переходе, необходимо выставить дополнительный заголовок Referrer-Policy
в ответе сервера.
Это можно сделать для всего сайта или отдельно на той странице, где отрисовывается ссылка на форму оплаты.
Значение заголовка должно быть no-referrer-when-downgrade
.
Шаг 7. Получение серверных уведомлений об оплате счетов или опрос статуса счета - что выбрать?
Сервис уведомлений позволяет понять, когда и по какому счету произошла оплата. Вам не нужно каждый раз отправлять запросы в QIWI, можно подключить сервис и получать уведомления автоматически. Но стоит обратить внимание на то, что уведомление может быть отправлено несколько раз даже в случае успешного ответа от вашего сервиса, поэтому это необходимо учитывать при разработке бизнес-логики отгрузки товара или услуги на вашей стороне.
Формат уведомлений описан в документации.
Данные приходят в теле запроса (body). Данные запроса хранятся в формате JSON.
Так как для отладки уведомлений тестовый контур не предусмотрен, рекомендуем выставлять счета на 1 рубль и оплачивать их самостоятельно.
Сервис уведомлений не является обязательным для интеграции, вы можете реализовать более простой вариант с опросом статуса счета. В этом случае перейдите сразу к шагу 11.
Условия интеграции с API уведомлений
Если вы пока не можете определиться, ознакомьтесь с условиями обслуживания интеграции с API уведомлений:
-
Мы устанавливаем на своей стороне таймаут на установление соединения – 2 секунды, и таймаут на получение ответа – также 2 секунды. Поэтому не рекомендуем внедрять долго выполняющуюся логику или логику, связанную с ожиданием (waiter, sleep), в процесс обработки уведомления.
Рекомендуем на стороне вашего сервера организовывать очередь и складывать в нее входящие запросы от QIWI с изменением статусов счетов, после чего отвечать QIWI на уведомление успешным статусом.
Обработку уведомлений в очереди и свою бизнес-логику стоит проводить в потоках, отдельных от приема уведомлений, чтобы успевать отвечать нам вовремя.
- Если в течение двух секунд мы не получаем ответ, то считаем отправку уведомления неуспешной, и будем его повторять в течение суток. Успешно доставленным уведомлением считается уведомление, на которое получен ответ со статусом 2ХХ ОК.
- Эта же механика работает, если ваш сервер для приема уведомлений недоступен. Мы будем пытаться отправить уведомление в течение суток, после чего данное уведомление получить будет невозможно.
-
Мы не можем гарантировать exactly once доставку уведомления, т.е. вам может прийти более одного уведомления об одной и той же успешной оплате счёта.
Чтобы обработать такие ситуации, вам необходимо проверять на своей стороне, присылали ли мы уже уведомление по оплате данного счета и была ли произведена отгрузка товара/услуги по этому счету вашему клиенту.
- В связи с неравномерностью нагрузки задержка уведомлений может быть до 1 дня (а в случае нештатной ситуации – до суток).
Если вы выберете механизм уведомлений для интеграции и получения сообщений об успешных платежах, мы все же рекомендуем использовать в дополнение к нему опрос статуса счета.
Шаг 8*. Настройка серверных уведомлений
Пример уведомления
POST /qiwi-notify.php HTTP/1.1
Accept: application/json
Content-type: application/json
X-Api-Signature-SHA256: J4WNfNZd***V5mv2w=
Host: example.com
{ "bill":
{
"siteId":"23044",
"billId":"1519892138404fhr7i272a2",
"amount":{
"value":"100",
"currency":"RUB"
},
"status":{
"value":"PAID",
"datetime":"2018-03-01T11:16:12"
},
"customer":{},
"customFields":{},
"creationDateTime":"2018-03-01T11:15:39",
"expirationDateTime":"2018-04-01T11:15:39+03:00"
},
"version":"1"
}
Пример получения уведомления
<?php
$log_file = fopen(__DIR__ . '/qiwi.txt', 'a+');
fwrite($log_file, print_r(json_decode(file_get_contents('php://input')), true).PHP_EOL);
fwrite($log_file, print_r(getallheaders(), true).PHP_EOL);
fclose($log_file);
?>
Уведомление представляет собой входящий POST-запрос.
Тело запроса содержит JSON-сериализованные данные счета (кодировка UTF-8).
- Проверьте, что IP-адреса, с которых QIWI отправляет уведомления, находятся в списке разрешенных:
- 79.142.16.0/20
- 195.189.100.0/22
- 91.232.230.0/23
- 91.213.51.0/24
- Убедитесь, что адрес сервера для приема уведомлений, указанный при создании ключей в личном кабинете, доступен из внешней сети (не только с вашего компьютера).
- Проверьте что ожидаете POST-запрос, для начала попробуйте логировать уведомления в файл на сервере.
- После успешного получения уведомления на стороне вашего сервера организуйте очередь и складывайте в нее входящие запросы от QIWI с изменением статусов счетов.
Если не приходят уведомления по счету
- Проверьте заголовки. По стандарту они регистронезависимы и ваш клиент может их изменить – смотрите документацию вашего клиента.
-
Попробуйте отправить себе уведомление, заменив в примере
https://test.com/notif.php
на URL, который вы указали при создании ключей для получения уведомлений.Пример:
curl --location --request POST 'https://test.com/notif.php' --header 'Accept: application/json' --header 'Content-type: application/json' --header 'Cookie: laravel_session=qePlTipWuv7zuRn0WTp9StDVFv2nAREe30gte7zk' --data-raw '{"bill":{"siteId":"2304","billId":"151989213804fhr7i272a2","amount":{"value":"1","currency":"RUB"},"status":{"value":"PAID","datetime":"2020-07-28T11:16:12"},"customer":{},"customFields":{},"creationDateTime":"2020-07-28T11:15:39","expirationDateTime":"2020-08-28T11:15:39+03:00"},"version":"1"}'
- Если вы используете сторонние сервисы, создающие готовые эндпоинты для приема запросов по https (например, Google Apps Scripts), убедитесь, что можно отправлять запросы в ваш сервис без дополнительной авторизации (в том числе анонимным пользователям).
-
Если вы не используете облачные сервисы для приема уведомлений (например, Amazon или Heroku), а настраивали свой веб-сервер и SSL в нем самостоятельно, проверьте ssl сертификат.
Если ssl сертификат для установления соединения по https выпущен не общеизвестным доверенным центром сертификации (например, Comodo, Verisign, Thawte и т.п.), проверьте, что ваш сервер при установке соединения отправляет полную цепочку сертификатов, включая доверенный корневой центр сертификации в начале цепочки.
Убедиться в том, что сервер присылает полную цепочку сертификатов, можно при помощи утилиты командной строки
openssl
, пример есть здесь.Полную цепочку сертификатов вы можете загрузить на сайте центра сертификации, выпустившего ваш сертификат.
Шаг 9*. Проверка подписи уведомлений
Настоятельно рекомендуем проверять уведомления на подлинность, т.к. мы не несем ответственности, если будут приходить поддельные уведомления от мошенников.
Процесс проверки подлинности уведомлений описан в документации.
Данные приходят в теле запроса (body). Данные запроса хранятся в формате JSON.
Для проверки подлинности используется механизм цифровой подписи. Подпись уведомления отправляется в HTTP-заголовке X-Api-Signature-SHA256
. Для формирования подписи используется механизм проверки целостности HMAC с хэш-функцией SHA256.
Пример
const validSignatureFromNotificationServer =
'07e0ebb10916d97760c196034105d010607a6c6b7d72bfa1c3451448ac484a3b';
const notificationData = {
bill: {
siteId: '270304',
billId: 'cc961e8d-d4d6-4f02-b737-2297e51fb48e',
amount: {value: 1, currency: 'RUB'},
status: {value: 'PAID'}
},
version: '3'
};
const merchantSecret = 'eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************';
qiwiApi.checkNotificationSignature(
validSignatureFromNotificationServer, notificationData, merchantSecret
); // true
<?php
$validSignatureFromNotificationServer = '07e0ebb10916d97760c196034105d010607a6c6b7d72bfa1c3451448ac484a3b';
$notificationData = [
'bill' => [
'siteId' => '270304',
'billId' => 'cc961e8d-d4d6-4f02-b737-2297e51fb48e',
'amount' => ['value' => 1, 'currency' => 'RUB'],
'status' => ['value' => 'PAID']
],
'version' => '3'
];
$merchantSecret = 'eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************';
/** @var \Qiwi\Api\BillPayments $billPayments */
$billPayments->checkNotificationSignature(
$validSignatureFromNotificationServer, $notificationData, $merchantSecret
); // true
?>
String merchantSecret = "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************";
Notification notification = new Notification(
new Bill(
"270304",
"cc961e8d-d4d6-4f02-b737-2297e51fb48e",
new MoneyAmount(
BigDecimal.ONE,
Currency.getInstance("RUB")
),
BillStatus.PAID
),
"3"
);
String validSignature = "07e0ebb10916d97760c196034105d010607a6c6b7d72bfa1c3451448ac484a3b";
BillPaymentsUtils.checkNotificationSignature(validSignature, notification, merchantSecret); //true
Assert.IsTrue(
condition: BillPaymentsUtils.CheckNotificationSignature(
validSignature: "07e0ebb10916d97760c196034105d010607a6c6b7d72bfa1c3451448ac484a3b",
notification: new Notification
{
Bill = new Bill
{
SiteId = "270304",
BillId = "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
Amount = new MoneyAmount
{
ValueDecimal = 1m,
CurrencyEnum = CurrencyEnum.Rub
},
Status = new BillStatus
{
ValueEnum = BillStatusEnum.Paid
}
),
Version = "1"
},
merchantSecret: "eyJ2ZXJzaW9uIjoicmVzdF92MyIsImRhdGEiOnsibWVyY2hhbnRfaWQiOjUyNjgxMiwiYXBpX3VzZXJfaWQiOjcxNjI2MTk3LCJzZWNyZXQiOiJmZjBiZmJiM2UxYzc0MjY3YjIyZDIzOGYzMDBkNDhlYjhiNTnONPININONPN090MTg5Z**********************"
)
);
Алгоритм проверки подписи:
-
Объединить значения следующих параметров уведомления в одну строку с разделителем
|
:invoice_parameters = {amount.currency}|{amount.value}|{billId}|{siteId}|{status.value}
где
{*}
– значение параметра. Все значения при проверке подписи должны трактоваться как строки. -
Вычислить HMAC-хэш c алгоритмом хэширования SHA256:
hash = HMAС(SHA256, invoice_parameters, secret_key)
где:
secret_key
– секретный ключ, при помощи которого был выставлен счёт;invoice_parameters
– строка из п.1. -
Сравнить значение заголовка X-Api-Signature-SHA256 с результатом из п.2.
В разрабатываемом коде заведите константу merchantSecret
и присвойте ей значение секретного ключа, который выпустили в личном кабинете. Значение должно быть то же самое, что в константе SECRET_KEY на шаге авторизации.
Проверьте, что ключ скопирован полностью, в конце должен быть знак "равно" =
.
Шаг 10*. Обработка уведомлений - отправка 200 OK
HTTP/1.1 200 OK
Content-Type: application/json
{
"error":"0"
}
После проверки уведомлений на подлинность отправьте QIWI ответ на уведомление успешным статусом.
Если вы наблюдаете задержки в получении уведомлений более 10 минут (увеличился баланс вашего кошелька или написал клиент, что оплатил, а уведомление так и не пришло) – рекомендуем в дополнение переключиться на поллинг статусов счетов – см. следующий шаг.
Шаг 11*. Проверка статуса перевода по счету
Пример
const billId = 'cc961e8d-d4d6-4f02-b737-2297e51fb48e';
qiwiApi.getBillInfo(billId).then( data => {
//do with data
});
<?php
$billId = '4fa5cb2a-942e-4e67-b552-ff10c71d6f8d';
/** @var \Qiwi\Api\BillPayments $billPayments */
$response = $billPayments->getBillInfo($billId);
print_r($response);
?>
String billId = "fcb40a23-6733-4cf3-bacf-8e425fd1fc71";
BillResponse response = client.getBillInfo(billId);
client.GetBillInfo(
billId: "fcb40a23-6733-4cf3-bacf-8e425fd1fc71"
);
curl --location \
--request GET \
'https://api.qiwi.com/partner/bill/v1/bills/cc961e8d-d4d6-4f02-b737-2297e51fb48e' \
--header 'accept: application/json' \
--header 'Authorization: Bearer <SECRET_KEY>'
Ответ
{
"siteId": "270305",
"billId": "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
"amount": {
"currency": "RUB",
"value": "200.34"
},
"status": {
"value": "WAITING",
"changedDateTime": "2020-07-29T19:31:06.846+03:00"
},
"comment": "Text comment",
"creationDateTime": "2020-07-28T19:31:06.846+03:00",
"expirationDateTime": "2020-08-8T19:31:06.846+03:00",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=ee3ad91d-cfb8-4dbf-8449-b6859fdfec3c"
}
Array
(
[siteId] => 270305
[billId] => 4fa5cb2a-942e-4e67-b552-ff10c71d6f8d
[amount] => Array
(
[currency] => RUB
[value] => 200.34
)
[status] => Array
(
[value] => WAITING
[changedDateTime] => 2018-07-12T10:31:06.846+03:00
)
[comment] => test
[creationDateTime] => 2020-07-28T19:31:06.846+03:00
[expirationDateTime] => 2020-08-28T19:31:06.846+03:00
[payUrl] => https://oplata.qiwi.com/form/?invoice_uid=ee3ad91d-cfb8-4dbf-8449-b6859fdfec3c
)
{
"siteId": "270304",
"billId": "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
"amount": {
"value": "199.90",
"currency": "RUB"
},
"status": {
"value": "WAITING",
"changedDateTime": "2020-07-28T16:03:09.062Z"
},
"comment": "Text comment",
"customer": {
"email": "example@mail.org",
"account": "349d5978-bccc-4e10-be7e-3ca0808237b7",
"phone": "79123456789"
},
"creationDateTime": "2020-07-28T16:03:09.062Z",
"expirationDateTime": "2020-08-28T16:03:08.668Z",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=b77618b4-746c-485f-8bb8-fff43ddef114",
"customFields": {
"apiClient": "java_sdk",
"apiClientVersion": "1.0.0"
}
}
new BillResponse
{
SiteId = "270304",
BillId = "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
Amount = new MoneyAmount
{
ValueString = "199.90",
CurrencyString = "RUB"
},
Status = new ResponseStatus
{
ValueString: "WAITING",
ChangedDateTime: BillPaymentsUtils.ParseDate("2020-07-28T16:03:09+03:00")
},
Comment = "test",
Customer = new Customer
{
Email = "example@mail.org",
Account = "349d5978-bccc-4e10-be7e-3ca0808237b7",
Phone = "79123456789"
},
CreationDateTime = BillPaymentsUtils.ParseDate("2020-07-28T16:03:09+03:00"),
ExpirationDateTime = BillPaymentsUtils.ParseDate("2020-08-28T16:03:08+03:00"),
PayUrl = new Uri("https://oplata.qiwi.com/form/?invoice_uid=b77618b4-746c-485f-8bb8-fff43ddef114"),
CustomFields = new CustomFields
{
ApiClient = "dotnet_sdk",
ApiClientVersion = "0.1.0"
}
};
{
"siteId": "9hh4jb-00",
"billId": "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
"amount": {
"currency": "RUB",
"value": "1.00"
},
"status": {
"value": "WAITING",
"changedDateTime": "2021-01-18T14:22:56.672+03:00"
},
"customer": {
"email": "test@tester.com",
"phone": "78710009999",
"account": "454678"
},
"customFields": {
"paySourcesFilter": "qw",
"themeCode": "Yvan-YKaSh",
"yourParam1": "64728940",
"yourParam2": "order 678"
},
"comment": "Text comment",
"creationDateTime": "2021-01-18T14:22:56.672+03:00",
"expirationDateTime": "2025-12-10T09:02:00+03:00",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=aa0fa2bb-5452-47ca-9190-cd9c1a73718f"
}
Так как в случае недоступности вашего сервера или сбоя на нашей стороне уведомления могут быть доставлены с задержкой, поэтому рекомендуем использовать метод проверки статуса оплаты счета как дополнение к механизму обработки уведомлений.
Также данный метод можно использовать как самостоятельный, т.е. после выставления счета раз в какой-то промежуток времени опрашивать статус этого счета. Этот метод проще интеграции с сервисом уведомлений.
См. также документацию с примерами проверки статуса оплаты счета.
Реализуйте вызов метода getBillInfo
. В параметрах необходимо передать идентификатор счета в вашей системе billId
, в результате будет получен ответ со статусом счета.
Шаг 12. Бизнес-логика
Счет в своем жизненном цикле проходит следующие статусы оплаты:
Статус | Описание | Комментарий |
---|---|---|
WAITING | Счет выставлен, ожидает оплаты | Нефинальный, ожидание оплаты или истечения срока действия |
PAID | Счет оплачен | Финальный (измениться не может) |
REJECTED | Счет отклонен | Финальный (измениться не может) |
EXPIRED | Время жизни счета истекло. Счет не оплачен | Финальный (измениться не может) |
Опираясь на статус счета, полученный в уведомлении или при помощи поллинга статуса, доработайте бизнес-логику вашего сайта.
Например:
- счет выставлен – появляется заказ в личном кабинете вашего пользователя,
- счет оплачен – обновляется статус заказа и, например, высылается письмо на почту пользователя и выполняется отгрузка товара или услуги.
Поздравляем, интеграция с QIWI для получения платежей закончена!
Дополнительно. Шаг 13. Отмена неоплаченных счетов
Пример
const billId = '60418b7e-1e95-4ac0-936e-0b98d7a7fdae';
qiwiApi.cancelBill(billId).then( data => {
//do with data
});
<?php
$billId = '60418b7e-1e95-4ac0-936e-0b98d7a7fdae';
/** @var \Qiwi\Api\BillPayments $billPayments */
$response = $billPayments->cancelBill($billId);
print_r($response);
?>
String billId = "fcb40a23-6733-4cf3-bacf-8e425fd1fc71";
BillResponse response = client.cancelBill(billId);
client.CancelBill(
billId: "fcb40a23-6733-4cf3-bacf-8e425fd1fc71"
);
curl --location \
--request POST \
'https://api.qiwi.com/partner/bill/v1/bills/cc961e8d-d4d6-4f02-b737-2297e51fb48e/reject' \
--header 'content-type: application/json' \
--header 'accept: application/json' \
--header 'Authorization: Bearer <SECRET_KEY>' \
--data-raw ''
Ответ
{
"siteId": "270305",
"billId": "60418b7e-1e95-4ac0-936e-0b98d7a7fdae",
"amount": {
"currency": "RUB",
"value": "200.34"
},
"status": {
"value": "REJECTED",
"changedDateTime": "2018-07-12T10:32:17.595+03:00"
},
"comment": "Text comment",
"creationDateTime": "2018-07-12T10:32:17.481+03:00",
"expirationDateTime": "2018-08-26T10:32:17.481+03:00",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=a3fe62b2-9962-4d9d-9025-0766fb492546"
}
Array
(
[siteId] => 270305
[billId] => 60418b7e-1e95-4ac0-936e-0b98d7a7fdae
[amount] => Array
(
[currency] => RUB
[value] => 200.34
)
[status] => Array
(
[value] => REJECTED
[changedDateTime] => 2018-07-12T10:32:17.595+03:00
)
[comment] => test
[creationDateTime] => 2018-07-12T10:32:17.481+03:00
[expirationDateTime] => 2018-08-26T10:32:17.481+03:00
[payUrl] => https://oplata.qiwi.com/form/?invoice_uid=a3fe62b2-9962-4d9d-9025-0766fb492546
)
{
"siteId": "270304",
"billId": "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
"amount": {
"value": "199.90",
"currency": "RUB"
},
"status": {
"value": "REJECTED",
"changedDateTime": "2018-11-03T16:03:09.062Z"
},
"comment": "Text comment",
"customer": {
"email": "example@mail.org",
"account": "349d5978-bccc-4e10-be7e-3ca0808237b7",
"phone": "79123456789"
},
"creationDateTime": "2018-11-03T16:03:09.062Z",
"expirationDateTime": "2018-12-18T16:03:08.668Z",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=b77618b4-746c-485f-8bb8-fff43ddef114",
"customFields": {
"apiClient": "java_sdk",
"apiClientVersion": "1.0.0"
}
}
new BillResponse
{
SiteId = "270304",
BillId = "fcb40a23-6733-4cf3-bacf-8e425fd1fc71",
Amount = new MoneyAmount
{
ValueString = "199.90",
CurrencyString = "RUB"
},
Status = new ResponseStatus
{
ValueString: "REJECTED",
ChangedDateTime: BillPaymentsUtils.ParseDate("2018-11-03T16:03:09+03:00")
},
Comment = "test",
Customer = new Customer
{
Email = "example@mail.org",
Account = "349d5978-bccc-4e10-be7e-3ca0808237b7",
Phone = "79123456789"
},
CreationDateTime = BillPaymentsUtils.ParseDate("2018-11-03T16:03:09+03:00"),
ExpirationDateTime = BillPaymentsUtils.ParseDate("2018-12-18T16:03:08+03:00"),
PayUrl = new Uri("https://oplata.qiwi.com/form/?invoice_uid=b77618b4-746c-485f-8bb8-fff43ddef114"),
CustomFields = new CustomFields
{
ApiClient = "dotnet_sdk",
ApiClientVersion = "0.1.0"
}
};
{
"siteId": "9hh4jb-00",
"billId": "cc961e8d-d4d6-4f02-b737-2297e51fb48e",
"amount": {
"currency": "RUB",
"value": "1.00"
},
"status": {
"value": "REJECTED",
"changedDateTime": "2021-01-18T14:36:17.65+03:00"
},
"customer": {
"email": "test@tester.com",
"phone": "78710009999",
"account": "454678"
},
"customFields": {
"paySourcesFilter": "qw",
"themeCode": "Yvan-YKaSh",
"yourParam1": "64728940",
"yourParam2": "order 678"
},
"comment": "Text comment",
"creationDateTime": "2021-01-18T14:22:56.672+03:00",
"expirationDateTime": "2025-12-10T09:02:00+03:00",
"payUrl": "https://oplata.qiwi.com/form/?invoice_uid=aa0fa2bb-5452-47ca-9190-cd9c1a73718f"
}
Вы можете отменять неоплаченные счета самостоятельно, используя метод cancelBill.
См. также документацию с примерами запросов для отмены неоплаченных счетов.