Pull to refresh

Система своевременного пополнения мобильного счета

Reading time 11 min
Views 3.8K

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

СОДЕРЖАНИЕ


ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ
ВВЕДЕНИЕ
1. ОПИСАНИЕ СЕРВИСОВ И СИСТЕМ
2. ЗАДАЧА ПРОЕКТА
3. ОПИСАНИЕ ПРОЕКТА
3.1. Средства для получения состояния лицевого счета
3.2. Средства для работы с ЯД
3.3. Средства для работы с Твиттером
4. РАБОТА И ЛОГИКА
ЗАКЛЮЧЕНИЕ
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ

ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ


ЛИСА — личный интернет-сервис абонента;
ЯД — Яндекс.Деньги;
Выпить ЯДу — получить денежные средства на Яндекс-кошелек;
БД — база данных.

ВВЕДЕНИЕ


Мне, как и многим, то и дело приходят мысли о том, что в нашей жизни есть множество вещей, которые чрезмерно сложны и запутаны, они требуют упрощения. Примерно такая мысль мне и пришла, когда, в очередной раз, я получил текст от своего мобильного оператора, о том, что баланс приближается к нулю и, в очередной раз, я пошел на ЯД, прошел авторизацию, нажал повторить платеж, подтвердил, указал платежный пароль, дождался выполнения, вышел из ЯД. Как-то многовато действий, вам не кажется? Мне показалось именно так, да и вообще, очень это утомительный процесс, нужно найти более простой способ, который бы мне подходил.

Хочется отметить, процесс так меня захватил, что теперь не уверен, — сэкономил ли я на всем этом время или проще было оставить все как есть? Однако, человек я такой, увлекаюсь во всякие интересные занятия быстро, поэтому большее удовольствия принес сам процесс реализации, чем конечный результат.

1. ОПИСАНИЕ СЕРВИСОВ И СИСТЕМ


ЛИСА— сервис, который позволяет абонентам Компании «Мотив» управлять текущим состоянием своего лицевого счета и пользоваться услугами при помощи сети интернет.

ЯД — электронная платёжная система. Многие магазины и компании реализуют у себя поддержку ЯД, что позволяет расплачиваться за товары и услуги легко и просто. «Мотив», как раз, одна из тех компаний, где реализована возможность пополнения баланса через ЯД.

Твиттер — система, позволяющая пользователям отправлять короткие текстовые заметки (до 140 символов), используя веб-интерфейс, SMS, средства мгновенного обмена сообщениями или сторонние программы-клиенты. Отличительной особенностью Твиттера является публичная доступность размещённых сообщений, что роднит его с блогами.

Вернуться к содержанию →

2. ЗАДАЧА ПРОЕКТА


Реализация системы пополнения баланса мобильного телефона с кошелька ЯД является основной задачей проекта, пополнение баланса должно происходить без какой-либо заданной периодичности. Мобильный счет должен пополняться только тогда, когда это необходимо.

Система должна включать в себя:
  1. Средства для получения состояния лицевого счета;
  2. Средства для работы с ЯД;
  3. Средства для работы с Твиттером.

Вернуться к содержанию →

3. ОПИСАНИЕ ПРОЕКТА


Проект размещен на площадке Мастерхост, хостинг получен бесплатно, в рамках программы поддержки студентов. Потребовалось обратиться в техническую поддержку, для включения curl и получения информации по поводу планировщика, так как на windows-тарифах данная услуга не предоставлялась. Было предложено добавить запись в crontab на unix-сервере, на что я и согласился. Я передал ссылку на скрипт и указал периодичность в 10 минут, после чего меня предупредили, — если скрипт будет создавать большую нагрузку, его придется отключить.

Вернуться к содержанию →

3.1. Средства для получения состояния лицевого счета

В первой версии состояние мобильного счета приходилось получать непосредственно через сайт ЛИСЫ, что занимало достаточно продолжительно время и не всегда давало результат, порой длительность операции достигала 10 секунд, что было просто неприемлемо.

Рисунок 3.1 — Сайт сервиса ЛИСА.

После длительного и беспощадного общения с технической поддержкой Компании «Мотив», мне стало известно, что в ближайшее время будет введен «Гаджет баланса», который и предназначен для отображения текущего баланса лицевого счета. Помимо этого, я получил информацию о том, что никакого внешнего интерфейса у сервиса не планируется, который, к слову, мне уже был не нужен, у меня была уверенность, что всю информацию я смогу достать из гаджета.

Рисунок 3.2 — Информация о «Гаджете баланса» и кнопка для его формирования.

Ранее мне не приходилось работать с гаджетами для Windows 7 или Vista, после установки\переустановки операционной системы я сразу отключал все это дело, так как не видел в этом необходимости, а на домашнем компьютере у меня старая-добрая Windows XP.

Рисунок 3.3 — Внутреннее строение «Гаджета баланса».

В каталоге «motiv_balance.gadget\js» находятся файлы:
  • balance.js — основная логика гаджета;
  • jquery.js — распространенная javascript-библиотека;
  • settings.js — реализация функций чтение и сохранение настроек гаджета.
Я принялся за изучение файла balance.js и сразу наткнулся на искомую функцию getInfo(), в которой формируется и отправляется запрос:
   _req = new ActiveXObject("Microsoft.XMLHTTP");
   /*don't change this sequence! it invokes non-ordering requests: xz why*/
   _req.open("GET", url, true);
ожидается ответ, далее передается в функцию showResponse(), которая и отвечает за обработку и отображение результата.

Как вы уже заметили, обращение к сайту оператора представляет собой не что иное, как обычный GET-запрос, который состоит из нескольких параметров:
  • uid — уникальный идентификатор, привязанный к номеру абонента;
  • b — тип запроса данных. Может принимать два значения: «a» — упрощенный отчёт (только состояние лицевого счета) или «b» — расширенный (состояние лицевого счета + остатки по абонементам\пакетам услуг, например, количество купленных SMS или MMS). Не уверен, что всё это работает, данное значение, мистическим образом, зависит от System.Gadget.docked;
  • a — случайное число.
Вот как это выглядит:
   b = System.Gadget.docked ? 'a' : 'b';
   a = Math.round(Math.random() * 10000);
   /*it should be global: no ideas why*/
   var url = "https://gadget.motivtelecom.ru/?uid=UID&b="+b+"&a=" + a;
В ответ получаем либо информацию о текущем состоянии счета, например:
   3=RUB:80р.45к.
где 3 — это номер версии гаджета, на момент написания статьи (var VERSION=3;), либо извещение об ошибке с подробным описанием:
   3=ERR

Вооружившись этими знаниями, я написал простейшую функцию, которая cURL-ом отправляет запрос.
    $req_lisa = array(
        CURLOPT_HEADER => 0,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_SSL_VERIFYPEER => 0,
        CURLOPT_SSL_VERIFYHOST => 0,
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_URL => LISA_URL
    );
    $ci = curl_init();
    curl_setopt_array ($ci, $req_lisa);
    $result_lisa = curl_exec($ci);
Время, требуемое на выполнение запроса и обработку результата, с 10 секунд уменьшилось до 0.26 секунды.

Вернуться к содержанию →

3.2. Средства для работы с ЯД

Для работы с ЯДом требуется:
  1. Изучить документацию;
  2. Зарегистрировать приложение (получить client_id);
  3. Получить временный токен (сроком менее 1 минуты);
  4. Получить токен авторизации (сроком на 1 год).

Рисунок 3.4 — Информация о зарегистрированном приложении.

Полученный токен авторизации ограничен 1 магазином (Компания «Мотив») и лимитом «1 тысяча рублей в неделю» с возможностью просмотра информации о текущем счете:
scope=payment.to-pattern("1122").limit(7,1000) account-info
Теперь в нашем распоряжении есть методы:
  • account-info — информация о состоянии счета, данный метод мною не использовался;
  • request-payment — запрос платежа;
  • process-payment — подтверждение платежа;
Для осуществления платежа, мы формируем шаблон платежа:
pattern_id=1122&PROPERTY1=908&PROPERTY2=9089XXX&sum=%s
и отправляем request-payment запрос на ЯД, если все верно, то получаем json-строку со значением «success» параметра status и желанный request_id, все остальное для нас, отличное от «success», равносильно неудаче и может зависеть от разных факторов, таких как: технические проблемы на стороне Яндекса, технические проблемы на стороне магазина или что-то еще.
Если всё в полном порядке, то отправляем уже process-payment запрос, содержащий request_id, тем самым мы подтверждаем платеж:

    $yad = json_decode($result, 1);
    if ( $yad['status'] == 'success' ) {
        $pay_fields = sprintf($process_fields, $yad['request_id']);
        curl_setopt($ci, CURLOPT_URL, YM_PROC_URL);
        curl_setopt($ci, CURLOPT_POSTFIELDS, $pay_fields);
        $result_pay = curl_exec($ci);
        curl_close($ci);
        return json_decode($result_pay, 1);
    }
В случае успешного или неудавшегося платежа, мы так же получаем json-строку с параметром status. При значении «success» параметра status в ответ приходит номер платежа и информация о состоянии счета.

Вернуться к содержанию →

3.3. Средства для работы с Твиттером

Для работы была выбрана готовая библиотека, требовалось лишь зарегистрировать свое приложение на сайте для разработчиков и получить все необходимые параметры для работы:
  • Consumer key
  • Consumer secret
  • Access token
  • Access token secret

Мне показалось, что будет очень уныло и не круто, если в твиттере будут публиковаться одни и те же твиты, поэтому я набросал набор фраз, которыми сейчас оперирует мое приложение-бот:
function GetRandomTwit() {
    $music_bands = array (
        'Queen', 'Coldplay', 'Muse', 'Justice', 'Duft Punk', 'Radiohead', 
        'Adele', 'Beatles', 'Red Hot Chili Peppers', 'Foo Fighters', 'Killers',
        'Gorillaz' );
    $rand_music_band = $music_bands[rand(0, sizeof($music_bands)-1)];
    $twi_mask = array (
        'На счету Лео, совершенно точно, есть %1$s #motiv #motivtelecom #wtf',
        'У Лео на телефоне: %1$s #motiv',
        'Баланс изменился, теперь составляет %1$s #motiv',
        '%1$s если быть точным #motiv',
        '#motiv хватит списывать деньги! На счету осталось %1$s',
        'Мы принимаем Яндекс.Деньги, вот они %3$s. Остаток: %1$s #motivtelecom',
        '%1$s и постоянно убывает, что делать? #motiv',
        'А баланс-то изменился, теперь там %1$s #motiv',
        'Переслушивал дискографию '.$rand_music_band.' и вдруг... на счету осталось уже %1$s #motiv',
        'БАЦ! И на счету уже %1$s #motiv #motivtelecom #whyyyyyy',
        'Иногда, #motiv списывает деньги просто так, возможно, это происходит прямо сейчас ;) Остаток %2$s руб.',
        'Зуб даю, списали случайно, Лео никуда не звонил! Ну вот, теперь уже %1$s',
        'Я не спец в финансовом деле, но знаю, что на счету Лео %1$s #motivtelecom',
        'Y U SO GREEDY? %2$s rub #motiv #motivtelecom',
        '#motiv у меня ВСЁ записано! Баланс %1$s #motivtelecom #yusogreedy',
        'Если бы знал, что ботам Лео ничего не платит, никогда бы у него не стал работать! Остаток %2$s руб.',
        'Сбор средств на GPRS-трафик ЯД: %3$s. Остаток на счету: %1$s #motiv',
        'Дааа, жаль у тебя не безлимит :( %2$s руб. #motiv #motivtelecom',
        'Я бот-меломан, сейчас слушаю '.$rand_music_band.', а на счету %2$s руб. #motiv #'.str_replace(' ','', strtolower($rand_music_band)),
        'А ведь раньше я работал почтовым роботом.. до чего я докатился! :( А баланс уже %2$s руб.',
        '#motiv дадите спокойно послушать альбом '.$rand_music_band.' или нет? Ок, %2$s руб на счету.',
        'И снова здравствуйте! На счету %1$s #motivtelecom',
        'Отправил, за счет Лео, СМС другу - спам-боту %) Теперь на счету %1$s #motiv',
        'Отправляйте мне свои предложение, в поле Примечание, на ЯД %3$s :) На счету %2$s руб.',
        'Я образованный бот, почему я должен это делать? Ок, Лео, на твоем счету %2$s руб. #motiv',
        'Работа - не волк, поэтому сообщу о балансе в следующий раз. #motiv',
        'BREAKING NEWS! Balance: %2$s rub.',
        'Я свободно владею несколькими языками, а должен заниматься этим! Лео, на твоем счету %2$s руб.'
    );  
    return ($twi_mask[rand(0, sizeof($twi_mask)-1)]);
}
Работает библиотека предельно просто:
$twitter = new Twitter(TWT_CON_KEY, TWT_CON_SEC, TWT_ACC_TOK, TWT_ACC_TOK_SEC);
$twitter->send(sprintf(GetRandomTwit(), GetSumWithPost($current_bal_value), $current_bal_value, YM_WALLET));

Вернуться к содержанию →

4. РАБОТА И ЛОГИКА


Главная задумка приложения — сделать плату своевременной, мне не нужно, чтобы баланс пополнялся каждый день на 50 или 100 рублей, ведь я могу просто не пользоваться в этот день телефоном и это можно было реализовать на ЯД. Дело оставалось за логикой работы, ведь все компоненты уже есть и работают.

Опустив лишние проверки, все сводится к понятному виду:
1. Проверяю текущий баланс, чтобы знать когда пополнять, надо всегда быть в курсе сколько сейчас денег на счету;
2. Определяю разницу между эталонным значением баланса (100 рублей) и текущим;
2.1. Разница больше заданного значения (20 рублей):
2.1.1. произвожу оплату;
2.1.2. пишу новое значение в БД;
2.1.3. пишу в Твиттер.
2.2. Разница меньше заданного значения (20 рублей):
2.2.1. Сравниваю текущее значение с последним, записанным в БД, если разница положительная, значит баланс пополнился:
2.2.1.1. пишу новое значение в БД;
2.2.1.2. пишу в Твиттер, что кто-то из вне пополнил баланс.
2.2.2. Сравниваю текущее значение с последним, записанным в БД, если разница отрицательная и меньше заданного значения (-5 рублей):
2.2.2.1. пишу новое значение в БД;
2.2.2.2. пишу в Твиттер, что баланс изменился.

Есть некоторые оговорки, которые пришлось учитывать:
— компания «Мотив» не принимает платежи менее 10 рублей — это означает, что разница между текущим состоянием счета и эталонным заданным значением должна составлять, как минимум, 10 рублей, чтобы произвести пополнение;
— сумма в 20 рублей взята для того, чтобы часто не писать в Твиттер о пополнении счета;
— разница в 5 рублей, о которой упоминалось выше, взята не просто так, ранее ограничения не было вообще и приложение сообщало, посредством Твиттер, каждые 10 минут о том, что баланс изменился на 7 копеек, а то и еще меньше — это сподвигло меня поставить некий ограничитель, сначала он был равен 1 рублю, но и этого оказалось мало, теперь ограничитель равен 5 рублям и меня ничто больше не беспокоит;
— если разница между текущим значением и эталонным оставляет более 100 рублей, то начинаем подозревать что-то неладное и пополняем баланс частями, сначала 100 рублей, потом оставшиеся деньги, по такому же принципу.
$pay_bot = (floor(BAL_LEVEL-$current_bal_value)>BAL_PAY_LEVEL_MAX)?BAL_PAY_LEVEL_MAX:(floor(BAL_LEVEL-$current_bal_value));
Будет достаточно времени, чтобы ответить на вопросы: «Куда делись деньги? Почему такой большой минус? Кто виноват?», выпить чашечку чая и, в конце концов, отключить бота.

    $pay_bot = (floor(BAL_LEVEL-$current_bal_value)>BAL_PAY_LEVEL_MAX)?BAL_PAY_LEVEL_MAX:(floor(BAL_LEVEL-$current_bal_value));
    if ( $pay_bot >= BAL_PAY_LEVEL ) {
        $pay_bot += BAL_UP_SUM;
        $ym_pay_state = YandexMoneyPay($pay_bot);
        if ( $ym_pay_state['status'] == YM_PAY_STAT ) {
            $twitter = new Twitter(TWT_CON_KEY, TWT_CON_SEC, TWT_ACC_TOK, TWT_ACC_TOK_SEC);
            $twitter->send(sprintf($plus_pay, GetSumWithPost($pay_bot), 
                GetSumWithPost($current_bal_value+$pay_bot), GetSumWithPost($ym_pay_state['balance'])));
        }
    } else {
        $pay_user = floor($current_bal_value - $last_bal_value);
        $bal_diff = $last_bal_value - $current_bal_value;
        if ( $pay_user > 0 or $bal_diff >= BAL_CHANGE_LEVEL ) {
            $twitter = new Twitter(TWT_CON_KEY, TWT_CON_SEC, TWT_ACC_TOK, TWT_ACC_TOK_SEC);
            if ( $pay_user > 0 )
                $twitter->send(sprintf($user_pay, GetSumWithPost($pay_user), GetSumWithPost($current_bal_value)));
            elseif ( $bal_diff >= BAL_CHANGE_LEVEL )
                $twitter->send(sprintf(GetRandomTwit(), GetSumWithPost($current_bal_value), $current_bal_value, YM_WALLET));
        }
    }
Отмечу, что на значение BAL_UP_SUM увеличивается любой платеж. Сейчас значение BAL_UP_SUM равно 1, вы видите, что выше все расчеты применяются с округлением в меньшую сторону, чтобы выйти за пределы 99 рублей с копейками следует добавить 1 рубль. Так же это значение можно увеличивать для того, чтобы приложение не докучало своей постоянно писаниной в Твиттер.

Вернуться к содержанию →

ЗАКЛЮЧЕНИЕ


Во время написания столкнулся с некоторыми вопросами, на которые так и не нашел ответ.
Я долго не мог понять, почему не могу сделать перевод на мобильный счет через ЯД, хотя в ответе на request-payment-запрос приходит значение «success». Оказалось, что приведенный в документации пример:
pattern_id=2904&phone-prefix=921&phone-number=9538ХХХ&sum=300.00
с содержанием полей phone-prefix и phone-number, не обязателен для каждого магазина, у Мотива эти параметры носят имена PROPERTY1 и PROPERTY2, у МТС или Мегафон эти поля тоже могут носить другие названия. Поля для Мотива мне подсказали модераторы клуба API ЯД, что и странно. Раз в ЯД знаю значения этих полей, почему просто, во время платежа, не могут переназначить со своих имен параметров на параметры магазина?

И еще один вопрос, он напрямую связан с первым. Когда я отправлял запрос с неверными именами полей мне, почему-то, приходило значение «success», так же приходил параметр request_id, по которому нужно подтверждать платеж. Такие платежи, конечно, не проходили, но почему в status мне приходил «success»? В руководстве разработчика ясно написано, что «success» — это успешное выполнение.

Буду рад, если кто-то ответит на эти вопросы.

Хочется выразить благодарность:
  • Сотруднику компании «Мотив» — Марине Neumann;
  • Модераторам клуба API Яндекс.Денег — Лине и hh;
  • Сотрудникам компании Мастерхост.
Результат работы: http://twitter.com/001011010

Вернуться к содержанию →

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ


1. API Яндекс.Денег. Руководство разработчика // материалы сайта api.yandex.ru/money/doc/dg/.
2. Клуб для разработчиков, использующих API Яндекс.Денег // материалы сайта clubs.ya.ru/moneyapi/.
3. Twitter. Сайт для разработчиков // материалы сайта dev.twitter.com.
4. ЛИСА. Гаджет баланса // материалы сайта lisa.motivtelecom.ru.
5. Материалы сайта ru.wikipedia.org.
Tags:
Hubs:
+26
Comments 26
Comments Comments 26

Articles