Согласования, боль и слезы больших компаний
Если упростить, в работе над любым проектом в компании участвует три группы:
- Заказчики. Отвечают за бизнес-идею проекта, финансовую оценку, продажи;
- Проектные менеджеры. Отвечают за техническую реализацию, определяют, какие группы она затронет, контролируют общие сроки и запуск проекта;
- Разработчики. Реализуют и запускают проект.
У каждого подразделения были свои подгруппы с очередями и приоритетами. Задача на серьезную доработку сайта затрагивала легион коллег:
- Платежный процессинг:
- Java-разработчики, отвечающие за платежи, API для крупных партнеров и стандартные внешние API,
- Oracle-разработчики, отвечающие за проектирование БД и хранимые процедуры;
- Веб-разработка:
- Java back-end + API разработчики для веба,
- JavaScript разработчики, особенно для SPA,
- Верстальщики;
- Q&A, дающее добро на итоговый выпуск продукта.
При таком подходе быстро запускались проекты, приоритетные для топов: для них выделяли отдельную проектную команду, которая запускала даже сверхсложные проекты вне приоритетов и очередей в минимальные сроки. Однако все остальные продуктовые задачи могли застревать в очереди на реализацию. Больше всего от этого страдало развитие уже запущенных продуктов.
Сейчас в QIWI формируются команды под конкретные продукты. При этом, к примеру, Java-разработчик из команды продукта, если это требуется, может вносить изменения в платежный процессинг, которые пройдут проверку и будут приняты. Раньше это должны были делать только разработчики процессинга, т.к. веб-разработчики переключались на другие проекты в своей очереди задач. Новый подход делает команду более гибкой, а время на реализацию новой функциональности значительно сокращается. Давайте посмотрим, как мы решили меняться.
Восемь лет спустя мы решились
Переделка основного протокола — ответственный шаг, но обязательный ввод номера телефона на стороне партнера усложнял интеграцию и оплату. Мы исследовали два варианта развития:
1. Сейчас наш серверный REST-протокол обязывает указывать номер телефона для выставления счета. Рассматривался вариант доработки для выставления счета на произвольный идентификатор или email.
Плюсы:
- Гибкий подход, удобный для потенциального обновления у существующих партнеров,
- Максимальная защита, т.к. протокол предполагает только межсерверное взаимодействие.
Минусы:
- Усложненная интеграция для новых партнеров, т.к. требуется выставлять счет перед переадресацией на страницу оплаты,
- Очень сложно делать массовые сервисы с оповещением о выставленных счетах,
- Требует значительной переделки архитектуры счетов в платежном процессинге.
2. У компании был http-протокол с вводом номера на стороне QIWI. Но в нем никак не проверялось, что счет выставляется именно партнером. Этот протокол идеально подходил для сбора пожертвований, но для крупных партнеров его нельзя было применять в таком виде. Выход заключался в добавлении подписи или хеша и их последующей проверке при переадресации на страницу оплаты счета.
Плюсы:
- Простая реализация для новых партнеров,
- Небольшие затраты на разработку со стороны команды.
Минусы:
- Могут появиться «одаренные» партнеры, которые будут хранить секретный ключ и вычислять подпись на клиенте.
После недолгих обсуждений был выбран второй вариант. Изначально служба безопасности и IT-подразделения отдавали предпочтение формированию подписи на основе открытого и закрытого ключей. Однако, вспомнив опыт интеграции даже с крупными партнерами и число возникающих вопросов и проблем при замене сертификатов, решили, что остановимся на проверке хеша sha512.
Встал вопрос, какие параметры хешировать? Самый простой вариант — упорядочить все по алфавиту, добавить секретный ключ и взять хеш. После анализа стало понятно, что могут возникнуть проблемы с комментариями и кодировками, поэтому сократили список до минимально необходимого числа параметров.
Оставался вопрос, как определять секретный ключ. Для basic-авторизации в REST-протоколе используется пара из API ID и API Password, которая позволяет гибко настраивать проверки для партнера. Для одного сайта можно завести несколько авторизационных пар и использовать их на разных серверах и версиях сайта.
Алгоритм и пример
В итоге, пришли к такому алгоритму:
- Формируем массив из параметров:
- api_id — id для определения секретного ключа,
- currency — валюта,
- from — id провайдера,
- summ — сумма,
- to (при наличии) — номер телефона,
- txn_id — уникальный ID транзакции на стороне провайдера;
- Добавляем в массив секретный ключ, связанный с api_id;
- Объединяем все значения параметров в строку, используя разделитель | между параметрами;
- Вычисляем хеш sha512;
- Добавляем в запрос строковый параметр signature в шестнадцатиричном виде в нижнем регистре.
Пример реализации на PHP.
// ID проекта, API_ID, API пароль получаются в настройках по url: https://ishop.qiwi.com/options/rest.action
$shopId = '260***';
$apiKey = '4683****';
$apiPassword = '1T1ea7****';
$CCY = 'RUB';
$amount = 1.12;
// Телефон в формате 71231234567. Необязательный параметр
$phone = null;
// ID транзакции
$txnId = 'habr'.rand(1,90000);
$params = [$apiKey, $CCY, $shopId, $amount, $txnId];
if (null !== $phone) {
$params = [$apiKey, $CCY, $shopId, $amount, $phone, $txnId];
}
// Добавляем в параметры секретный ключ
$params[] = md5($apiKey);
// Формируем строку из параметров с разделителем "|"
$paramsStr = implode('|', $params);
// Вычисляем хеш
$signature = hash('sha512', $paramsStr);
// Формируем query стринг
$query = http_build_query([
'from' => $shopId,
'to' => $phone,
'txn_id' => $txnId,
'summ' => $amount,
'api_id' => $apiKey,
'ccy' => $CCY,
'signature' => $signature
]);
// Собираем итоговый url для клиента
$url = 'https://bill.qiwi.com/order/external/create.action?'.$query;
Коллективный R&D
Предлагаю, в качестве эксперимента, вместе решить, как развивать протокол. Мы не решили несколько технических вопросов и предлагаем вам подумать вместе с нами, как развивать протокол. Для этого в конце статьи добавлено голосование.
Основной проблемой остаётся параметр lifetime, отвечающий за время жизни счета. В существующем протоколе он передавался в минутах, а счет выставлялся сразу после переадресации. В новом — счет может выставляться существенно позже, особенно если ссылка была отправлена по электронной почте. Поэтому, скорее всего, требуется добавить новый параметр с абсолютной датой. К примеру, в формате ISO 8601 (2016-09-12T00:00:00), который используется в REST. Но нужны ли там секунды? И просить ли кодировать ‘:’ в ‘%3A’, либо заменить его на ‘-’, т.к. многие до сих пор передают success_url без url кодирования и стучатся в поддержку с вопросами. Скорее всего, новый параметр стоит также включать в вычисляемую подпись.
Обновленный протокол с трудом, но позволяет делать массовые рассылки. Основная проблема в уникальном id транзакции, который необходимо будет генерировать при создании ссылки. Для массовых рассылок с покупкой конкретного предмета это может быть затруднительно. Решением могла бы стать совокупность id покупки и аккаунта пользователя в магазине, но тогда пользователь сможет купить что-то только один раз. Проблему можно было бы решить сделав новые параметры и убрав ограничение по уникальности. Правда, есть сомнения, что это требуется рынку. Проголосуем?
На десерт
Новый протокол уже готов для подключения партнеров, но пока активируется только через службу поддержки. Если вы хотите принять участие в тестировании, пишите на cms@qiwi.ru или обращайтесь в поддержку.
Оценить текущее состояние страницы оплаты вы можете здесь. С днем программиста!
P.S. Приглашаю Java разработчика с хорошим опытом реализации REST API присоединиться к нашей команде по запуску нового ishop.qiwi.com и качественно перевернуть мир для тысяч наших партнеров уже в этом году.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (41)
Apilen
12.09.2016 15:40А вы собираетесь когда-нибудь передавать сумму списания денег с кошелька, если она проходит в валюте отличной от валюты баланса?
Например, у меня есть баланс в 100 EUR на кошельке, я по api делаю out транзакцию 100 RUB для пользователя. В результате я не знаю какую сумму в EUR вы снимаете c моего счета. Я знаю, что по вашим правилам это произойдет по курсу ЦБРФ+1%, но для нормальной сверки в режиме реального времени мне необходимо знать сколько конкретно денег в какой валюте вы с списываете с моего кошелька.
Почему так сложно в ответном запросе передавать сумму списания?GEG
12.09.2016 15:48В ответном запросе — это в уведомлении вашего сервера или в запросе информации по счету?
Мы давно хотим внести ряд изменений в уведомление, но там считается подпись по всем полям и пока нет версионности в протоколе, поэтому сейчас, при изменении числа параметров, есть риск возникновения проблем у неопределенного числа партнеров. Есть желание добавить поддержку версий и сделать, в частности, возврат суммы списания, но это потребует усилий квалифицированных Java разработчиков, которых мало и мы ищем. :)qiwi_russia
12.09.2016 16:06Да-да, ищем;) Описание одной из этих вакансий можно найти в Моем круге: https://moikrug.ru/vacancies/1000028469
TimsTims
12.09.2016 16:25+2Довольно тяжело читается, статья выглядит довольно сумбурно, будто бы её писали сразу несколько разных человек — маркетинг, пиар, безопасник, ПМ и разработчик:
Сначала начинаете за здаривие — мол меняется протокол, затем что-то про ПМов, разработчиков, верстальщиков и как сложно вытащить разработчика. Причем, от знания, что у вас есть Java-разработчики и Oracle-разработчики и что они делают — в статье никак это не пригождается. И никак к теме про новый протокол для партнера не относится…
Затем наконец-то самое интересное про протокол: вроде интересно, а потом возникает неизвестное слово «партнер» — кто такие партнеры. Сначало естественно кажется — это те, кто вложился с вами в бизнес, т.е. вроде как руководство Qiwi. Затем, думаешь что это какие-то рекламщики, развивающие киви, но тоже по контексту не подходит…
Только под конец понял, что «партнеры» для вас — это Юр.лица имеющие личный кабинет и продающие товары через интернет через QiWi. А для обычного читателя это так и останется непонятным (вы ведь на большую интернет-аудиторию сообщаете новость, а не просто на своём сайте, чисто для своих клиентов?).
> После недолгих обсуждений был выбран второй вариант
Честно. Пытался 5 раз найти этот самый «второй вариант». Не нашел, где вы упоминали «первый варианТ» и «второй вариант».
> т.к. многие до сих пор передают success_url без url кодирования
Я считаю, что раз партнеры настолько низкопрофильные, что не могут сделать urlencode, то требовать от них более сложных вещей, вроде времени по ISO 8601 — считаю слишком непосильной задачей. Предлагаю вам пойти навстречу чайникам, и разрешить им передавать в этом параметре несколько вариантов значений — в unixtime, либо в ISO 8601. Определить на стороне бэкэнда, что нам пришло — не такая уж непосильная задача, а для пользователя вы решите еще одну проблему с передачей времени. Ну и да, до int32 при unixtime(на старых системах) проработает до 2038года. К тому времени, можно будет и еще раз протокол обновить…ecne
12.09.2016 18:24Статья при этом написана лучше, чем документация для разработчиков. Это понятно — техническому писателю в структуре процесса места попросту нет. Загадка в другом: что в этих текстах такого секретного, что скачать их можно только имея доступ к аккаунту магазина?
TimsTims
12.09.2016 18:50Видимо, чтобы было меньше скрипт-кидди, пытающихся «взломать» action-url, о котором здесь любезно рассказали.
ecne
12.09.2016 19:00Очень мало может вызвать доверия использование такой защиты. 9 из 10 платежных систем вешают ссылку на документацию не то, что на главной странице — на первом экране.
GEG
12.09.2016 18:59Без возможности настроить магазин, в большинстве случаев и документация бесполезна. Поэтому, увидев минимальное число скачиваний из неавторизованной зоны, просто убрали этот раздел. Планируем в этом году запустить портал на основе swagger, где будет и документация и интерактивные формы для выполнения запросы, которые решат проблему.
GEG
12.09.2016 18:57Довольно тяжело читается, статья выглядит довольно сумбурно, будто бы её писали сразу несколько разных человек — маркетинг, пиар, безопасник, ПМ и разработчик
Статью писал я и в разное время получил опыт в нескольких сферах. Маркетинг и pr, конечно, проверяли. Старался показать наиболее интересные вопросы, которые возникали в разное время, пока запускали проект, но видимо, получилось, действительно сумбурно.
Первый раздел отвечал на вопрос — почему думали 5 лет, а не сделали сразу.
Про партнеров: внутри они называются мерчантами, но это сленг. Более адекватных определений не нашли и в прошлой статье таких вопросов не возникало. Спасибо, дам определение.
Честно. Пытался 5 раз найти этот самый «второй вариант».
Первый вариант — доработка REST запроса. Самое начало главы.
Определить на стороне бэкэнда, что нам пришло — не такая уж непосильная задача
Спасибо. Хорошее предложение. Добавил в голосование.
bustEXZ
12.09.2016 16:36Даже тут комиссию берете? :)
Ваша покупка 1.23
Сумма к оплате 1.24GEG
12.09.2016 17:23В — внимательность! На pull протоколе никогда и нигде нет верхней комиссии при оплате с кошелька. Это единственный тестовый провайдер, где проверяется, что она теоретически может быть, т.к. это заложено в интеграционные тесты. :)
VladislavWA
12.09.2016 16:59+1Документация доступна только из личного кабинета — это очень удобно (особенно с учетом того, что старые аккаунты вы отключили).
Что со сроком жизни «устаревших» протоколов? Отключите или будете поддерживать?
И баг-репорт: научите разработчиков корректно обрабатывать кавычки (и прочие символы) в поле комментария к счетуGEG
12.09.2016 17:281) Какую документацию предоставить? Аккаунты на вход блокируются только службой антифрода и крайне редко. Старые аккаунты мы массово не отключали.
2) SOAP и REST протоколы будут поддерживаться и дальше. Описанное в статье расширение протокола HTTP это лишь приятное дополнение к одному из них, т.к. оповещение оповещение об оплате счета в любом случае должно прийти на сервер.
3) Спасибо, воспроизведем и поправим.VladislavWA
12.09.2016 17:561. Собственно
СкриншотGEG
12.09.2016 18:421. Для доступа к именному кошельку требуется получить упрощенную идентификацию на qiwi.com для связанного с ним номера телефона. После этого вы сможете войти в ishop.
2. Мы до сих пор поддеживаем url для выставления счета, которые были сделаны в 2008 году. :) Исключение — вынужденное отключение XML протокола год назад, т.к. по нему участился фрод, а сам протокол был закрыт для новых подключений более 4 лет.VladislavWA
12.09.2016 19:13Оно мне предлагает регистрироваться заново (или это ошибка «перевода»?), а не восстанавливать доступ, имхо, это существенная разница. (предполагаю, что связанные с аккаунтом тестовые магазины так же канули в лету)
GEG
12.09.2016 20:05Ошибка перевода. А чем переводите? Достаточно зайти на qiwi.com с номера +7916***49 и идентифицироваться в настройках профиля. Все ваши тестовые проекты будут доступны.
VladislavWA
12.09.2016 20:34Собственно, перевожу с русского на русский глазами. Как я понимаю, qiwi кошелек был удален, поэтому лишь один вариант — регистрация как нового пользователя, но вот связь (вероятно, по номеру телефона) с тестовыми магазинами осталась, для оживления доступа к которым пришлось таки ввести паспортные данные (внутренний параноик в ужасе и заранее думает о способах удаления/закрытия аккаунта).
GEG
13.09.2016 11:00Именной кошелек — это кошелек физического лица. Переводы между физическими лицами должны быть идентифицированы, согласно закону, который был принят в 2014 году. Поэтому для продолжения работы, к сожалению, вы обязаны указать свои данные в личном кабинете. Идентификацию привязанного кошелька можно провести по ссылке после авторизации.
datacompboy
12.09.2016 18:34+2Вы до сих пор не показываете лимиты на операции. Это невозможно ж пользоваться!
Показывает, лимит «250000 руб», пытаюсь оплатить 30к — говорит «превышен лимит».
И так и сяк, и да пошли вы, выкрутился иначе.GEG
12.09.2016 18:44Спасибо. Ошибка достаточно редкая. Не думали, что она доставляет такие проблемы. Постараемся что-нибудь придумать с командой UX, т.к. для большинства пользователей эта информация не нужна.
VladislavWA
12.09.2016 18:52+1Критика:
- Формат номера телефона: Телефон в формате 71231234567. Необязательный параметр, в остальных местах строго с + (если быть совсем точным, то tel:+71231234567). Еще один вариант передачи или наоборот послабление в строгости формата?
- Валидация формы не полная: получаем Ошибка в параметрах запроса без уточнения, что сумма со многими нулями не по зубам (и ограничение на прием платежей более 15к уже не действует?), что валюты с переданным кодом не существует и т.п. (видимо предварительная проверка формы )
- Ну и несколько раз получил Оу! Сервер барахлит. Обновите страницу с кодом 403.
GEG
13.09.2016 13:34- Формат номера оставили тот, который использовался ранее в протоколе HTTP без подписи. С плюсом номер передается в REST протоколе.
- Валидацию расширим, спасибо. Ограничение на платежи больше 15 000 сняли для большинства провайдеров. В этом месяце снимаем для всех, кроме именных кошельков, поднимая лимит на выставление счета до 250 000 р.
- Хм. Такая ошибка выдается при отсутствии связи с сервером. Обратим внимание, спасибо. Мы сейчас налаживаем мониторинг ошибок на стороне клиента. Это одна из новых ошибок, которая раньше обрабатывалась некорректно.
rumkin
12.09.2016 20:49+2Еще добавлю про $API_PWD. Вообще-то 2016 год, криптоалгоритмы расписаны и разжеваны, ваш алгоритм верификации из 2012. На секундочку – вы финансовый проект, для которого безопасность на первом месте. Пора взрослеть и переходить на подпись транзакций.
GEG
13.09.2016 14:25Подписи на основе сертификатов у нас используется в нестандартных api для крупных партнеров. По опыту могу сказать, что это всегда пляски с интеграцией, поэтому для массового api решили использовать простое для реализации, но защищенное решение. Вы не могли бы привести примеры других публичных API, которые уже перешли массово на подпись с сертификатами?
p.s. Больше всего за опыт интеграции я был удивлен, когда европейским партнерам пришлось кидать ссылку на wiki про url encoding… Индусы на аутсорсе не вызывали удивление с таким же непониманием.rumkin
13.09.2016 16:46Массово: все крипто-валюты, крипто-биржи и т.п. Но продукт, который перешел бы на подписи не назову. Это дает вам дополнительное конкурентное преимущество на ровном месте. Стоимость перехода на подписи не так велика, как кажется. Посудите сами весь алгоритм цифровой подписи включает один дополнительный вызов содержащий два параметра: хэш данных запроса и секретный ключ.
А для преодоления трудностей в интеграции я бы советовал разработать SDK для самых популярных платформ, все-таки низкий порог вхождения – в ваших же интересах. Так же не лишним было бы сделать песочницу, чтобы наглядно продемонстрировать механизм работы.
Big_Shark
13.09.2016 06:22Я конечно все понимаю, вы крупная финансовая компания, и PHP не ваш конек, но блин, оформите хотя-бы PHP код как следует в статье.
GEG
13.09.2016 11:03К сожалению, PHP разработчиков у нас нет, да. Код писал я для примера, т.к. это наиболее наглядный способ описания. Понимаю, что PHP ушел далеко вперед с 2010 года, когда я им занимался. не могли бы вы уточнить что предлагаете сделать, либо кинуть примером правильно оформленного кода?
Big_Shark
14.09.2016 08:57+1Переименова переменные, немного подправил код стайл, немного поменял логику, а вообще конечно лучше выкатить нормальную библиотеку для работы с АПИ
// ID проекта, API_ID, API пароль получаются в настройках по url: https://ishop.qiwi.com/options/rest.action $shopId = '260***'; $apiKey = '4683****'; $apiPassword = '1T1ea7****'; $CCY = 'RUB'; $amount = 1.12; // Телефон в формате 71231234567. Необязательный параметр $phone = null; // ID транзакции $txnId = 'habr'.random_int(1,90000); $params = [$apiKey, $CCY, $shopId, $amount, $txnId]; if (null !== $phone) { $params = [$apiKey, $CCY, $shopId, $amount, $phone, $txnId]; } // Добавляем в параметры секретный ключ $params[] = md5($apiKey); // Формируем строку из параметров с разделителем "|" $paramsStr = implode('|', $params); // Вычисляем хеш $signature = hash('sha512', $paramsStr); // Формируем query стринг $query = http_build_query([ 'from' => $shopId, 'to' => $phone, 'txn_id' => $txnId, 'summ' => $amount, 'api_id' => $apiKey, 'ccy' => $CCY, 'signature' => $signature ]); // Собираем итоговый url для клиента $url = 'https://bill.qiwi.com/order/external/create.action?'.$query;
P.S. Ну и странное же у вас API, как будто не новое, а старое.GEG
14.09.2016 23:53Спасибо! Обновил пример. Правда, вместо random_int оставил rand, т.к. random_int появился только в php 7, а пятая версия, как понимаю, все еще очень популярна.
Лучше выкатить нормальную библиотеку для работы с АПИ
Полностью согласен. Давно над SDK думали, но без обновления ishop.qiwi.com они были почти бессмысленны. Теперь, после доработок API и начала работ над новым ishop, есть желание вернуться к этой теме и заказать разработку open source SDK и плагинов для популярных платформ для REST API. Есть желание поучаствовать?
Ну и странное же у вас API, как будто не новое, а старое.
Для получения обратной связи и написал статью. :) Есть предложения по улучшению?
Big_Shark
15.09.2016 08:03Ну как минимум делать сигнатуру используя тот же query формат, и ключи в алфавитном порядке, поменять summ на amount, также желательно переименовать id и password в client_id и client_secret.
Зачем тут md5 для ключа не понятно, он нигде не светится.
Зачем shopId тоже не понятно, для разных магазинов, разные client_id и client_secret должны быть.
Где returnUrl задается тоже не вижу, хотя во всех платежках с которыми я работал, он был, а часто их было даже два.
Также рекомендую использовать ключ currency вместо CCY. PayPal и Stripe используют его.
Можно попробовать написать драймер к omnipay, там все достаточно просто и легко.
ange007
13.09.2016 13:47[offtop]Вы бы лучше разобрались со своей дурацкой системой безопасности по отношению к пользователям стран в которых имеются предоплаченные номера. Я с Украины и мне заблокировали кошелёк и потребовали договор с оператором — которого у меня нет, и быть не может. Хорошо что там денег не много на кошельке оставалось.[offtop]
А вот «текущее состояние страницы оплаты» — ужасное.
Нет никакой информации по тому что это вообще за страница — и для чего она, просто просит ввести номер телефона и комментарий, и после этого сразу высылает сообщение с кодом.GEG
13.09.2016 14:42Спасибо, страницу с вводом номера телефона на стороне Qiwi причешем, добавив данные о платеже. Для получения отзывов и выкладывали. Жаль, что пасхалка ко дню разработчика исчезла. Сейчас клиент в магазинах выбирает Qiwi, вводит номер телефона и попадает сразу на смс авторизацию.
rumkin
В стандарте ISO8601 разделители (кроме T) являются опциональными и могут быть редуцированы, т.о. валидным значением является и
1970-01-01T0000
и19700101T0000
. Так что для удобства я бы использовал полный формат и время без двоеточий:1970-01-01T00:00
1970-01-01T0000
Так получится избежать URL-encoding, что будет удобным при ручном вводе.
GEG
Спасибо! Добавил вариант в голосование.
Filippok
Почему нет варианта с Unix time?
GEG
Пятый вариант с «timestamp»?
Filippok
Без сочетаний с вариантами выше. Просто timestamp.