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

Тогда я решил использовать Money API, которое позволит получить всю необходимую информацию.

Для Android есть отдельный SDK, но бегло просмотрев его, я понял, что это просто Activity, которое позволяет совершать платежные операции. Может быть позже я подробно разберусь с этим и сделаю отдельную статью.

А вот Java SDK - это как раз то, что мне нужно. С его помощью можно обращаться к методам Money API и получать данные.

Для получения полной информации по Money API рекомендую ознакомиться с официальным хелпом - https://tech.yandex.com/money/doc/dg/concepts/About-docpage/.

Также я нашел подробный PDF документ на русском - https://tech.yandex.ru/money/doc/dg/yandex-money-dg.pdf.

 

Я создал небольшой пример приложения, которое запрашивает детальные данные по операциям. Я буду использовать этот пример для объяснения принципов работы Money API. Исходники вы можете посмотреть на github - https://github.com/startandroid/YandexMoneyJavaAPI. Если вам нужны какие-то другие данные, а не операции, то этот пример и хелп помогут вам достичь вашей цели.

В примере не реализованы прогресс-бары, обработка поворота экрана и прочее, чтобы не усложнять код. Не рассматривайте его как Best Practice.

 

 

Регистрация приложения

Для начала нам необходимо зарегистрировать свое приложение в Яндекс Деньги.

Это выглядит примерно так


Название - имя приложения, которое будет отображаться пользователю на веб-странице авторизации.

Адрес сайта - ссылка на сайт, связанный с приложением.

Email-для связи и логотип - все понятно

Redirect Uri - тут необходимо указать какой-нибудь сайт, можно и не существующий или совсем левый какой-нибудь типа http://example.com. Этот адрес нигде не будет отображаться или открываться. Он будет использован как текстовый ключ.

Если вы используете мои исходники, то поместите ваше значение Redirect Uri в константу Constants.REDIRECT_URI.

 

После подтверждения вы получите идентификатор приложения.

Этот идентификатор вам необходимо поместить в Constants.CLIENT_ID. По нему сервер поймет какое именно приложение хочет получить данные.

 

 

Авторизация

Самое сложное в работе с Money API - это OAuth2 авторизация. Если вы раньше с ней не сталкивались, то она может показаться сложной и запутанной. Я попробую объяснить ее достаточно подробно.


В авторизации участвуют три стороны:

  • пользователь Яндекс-денег
  • Android приложение
  • сервер Яндекс-денег

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

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

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

 

Схема авторизации с токеном выглядит так

 

Подробно опишу все шаги:

1) Пользователь сообщает приложению, какие права он готов ему предоставить. На основе этих данных приложение формирует специальную HTTP-ссылку авторизации.

2) Приложение просит WebView отобразить содержимое сформированной ссылки

3) WebView открывает страницу из Яндекс-денег. На этой странице сервер предлагает пользователю залогиниться в Яндекс-деньги под своей учетной записью

4) Пользователь логинится, а затем попадает на страницу, где от него требуется подтвердить, что он готов предоставить запрашиваемые права данному приложению.

5) Сервер выполняет редирект на ссылку, в которой содержится специальный код. Приложение перехватывает эту ссылку в WebView и не открывает страницу, а извлекает из ссылки код.

6) Используя код приложение запрашивает у сервера токен (уже обычным HTTP-запросом, без WebView).

7) Сервер выдает приложению токен

8) Используя токен, приложение может получать от сервера данные, на которые пользователь дал ему права.


Вся эта схема реализована в примере в AuthActivity. И мы сейчас разберем код для каждого шага.

 

 

Шаг 1

Реализован в методе startAuthorization

    public static void startAuthorization(Activity activity, int requestCode, Scope... scopes) {
        AuthorizationParameters.Builder builder = new AuthorizationParameters.Builder()
                .setRedirectUri(Constants.REDIRECT_URI);
        for (Scope scope : scopes) {
            builder.addScope(scope);
        }
        AuthorizationParameters parameters = builder.create();

        AuthorizationData data = AppController.getApiClient().createAuthorizationData(parameters);
        String url = data.getUrl() + "?" + new String(data.getParameters());

        Intent intent = new Intent(activity, AuthActivity.class);
        intent.putExtra(Constants.EXTRA_URL, url);
        activity.startActivityForResult(intent, requestCode);
    }

В параметре scopes передается набор прав, которые пользователь готов предоставить приложению.

В AuthorizationParameters.Builder передаем REDIRECT_URI и scopes, и методом create создаем AuthorizationParameters. Этот объект хранит параметры, которые будут использованы при формировании ссылки на страницу авторизации.

В метод ApiClient.createAuthorizationData передаем эти параметры и получаем AuthorizationData. Из этого объекта достаем url и параметры, склеиваем их и получаем готовый адрес страницы авторизации.

Помещаем его в Intent и методом startActivityForResult открываем AuthActivity.

 

Метод startAuthorization вызывается из MainActivity, чтобы начать процесс авторизации:

        AuthActivity.startAuthorization(this, REQUEST_CODE_AUTH, 
                Scope.OPERATION_HISTORY, Scope.OPERATION_DETAILS);

Передаем права на историю операций и детальную информацию по операциям.

Кроме этих двух вы можете использовать следующие права:

  • ACCOUNT_INFO (получение информации об аккаунте: номер, баланс, аватар и т.п.)
  • INCOMING_TRANSFERS (возможность принимать или отклонять входящие платежи)
  • PAYMENT_P2P (выполнение денежных переводов)
  • PAYMENT_SHOP (осуществление платежей в магазинах)

Итак, мы открыли AuthActivity и передали ему ссылку.

 

 

Шаг 2

Передаем полученную ссылку в WebView

    private void requestAuthorization() {
        String url = getIntent().getStringExtra(Constants.EXTRA_URL);
        if (URLUtil.isValidUrl(url)) {
            webView.loadUrl(url);
        } else {
            returnError(getString(R.string.url_is_not_valid));
        }
    }

 

 

Шаг 3

Открывается страница авторизации

Вводим свои учетные данные и входим в систему

 

 

Шаг 4

После логина отображается страница, на которой пользователь должен подтвердить, что он готов предоставить права на историю и детальную информацию по операциям приложению Report.

Т.е. здесь будут перечислены те права (Scope), что вы передали в AuthActivity.

Жмем продолжить

 

Необходимо подтвердить с помощью СМС или установленного приложения Яндекс Деньги.

 

 

Шаг 5

После всех подтверждений сервер выполняет редирект на технический адрес, который содержит в себе REDIRECT_URI и специальный код.

По наличию REDIRECT_URI мы поймем, что этот адрес - технический и его не надо открывать в WebView. Вместо этого нам надо извлечь из него код.

С помощью своего WebViewClient мы перехватываем все адреса, которые открывает WebView.

 

    private class AuthWebViewClient extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String uri) {
            // если URL содержит REDIRECT_URI, значит это нам пришел код
            // и WebView не надо открывать этот URL

            if (uri.contains(Constants.REDIRECT_URI)) {
                // парсим URL, чтобы извлечь из него код
                AuthorizationCodeResponse response = null;
                try {
                    response = AuthorizationCodeResponse.parse(uri);
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                    returnError(e.getMessage());
                }
                if (response != null) {
                    if (response.error == null) {
                        // используя код получаем токен
                        getToken(response.code);
                    } else {
                        returnError(response.error.getCode() + ", "
                                + (response.errorDescription == null ? "" : response.errorDescription));
                    }
                }
                return true;
            }
            return false;
        }
    }

Как только мы видим, что адрес содержит REDIRECT_URI, мы парсим этот адрес с помощью AuthorizationCodeResponse, извлекаем из него код (response.code) и передаем в метод getToken.

 

 

Шаг 6

Метод getToken выполняет запрос, в котором передает на сервер код, а обратно получает токен.

Этот запрос реализован в классе Callable, который затем запускается с помощью RxJava

    // Callable обертка выполнения запроса на получение токена
    private class TokenRequest implements Callable<Token> {

        private final String code;

        public TokenRequest(String code) {
            this.code = code;
        }

        @Override
        public Token call() throws Exception {
            return apiClient.execute(new Token.Request(code, Constants.CLIENT_ID, Constants.REDIRECT_URI));
        }
    }

Запрос мы выполняем с помощью apiClient. Передаем код, CLIENT_ID и REDIRECT_URI.

 

 

Шаг 7 и 8

В методе getToken в Observer.onNext нам приходит токен

                    @Override
                    public void onNext(Token token) {
                        if (token.error == null) {
                            // токен получен! сохраняем его в префы
                            SharedPreferences prefs = AppController.getPreferences(AuthActivity.this);
                            prefs.edit().putString(Constants.EXTRA_TOKEN, token.accessToken).apply();
                            // и отдаем в apiClient, чтобы он был авторизован
                            apiClient.setAccessToken(token.accessToken);
                            returnSuccess();
                        } else {
                            returnError(token.error.getCode());
                        }
                    }

Полученный от сервера токен сохраняем в Preferences, чтобы использовать при следующем запуске приложения. А также передаем этот токен в apiClient.

apiClient - это синглтон. Он используется по всему приложению для запросов к серверу. Мы передали ему токен и теперь он авторизован для получения данных с сервера. Мы будем использовать его в MainActivity для получения данных по операциям.

 

 

Получение данных

У нас есть токен. Вернее, у нас есть авторизованный apiClient с токеном. А значит, у нас есть права чтобы запросить у сервера историю операций и детальную информацию по каждой операции.

Вся логика получения данных реализована в MainActivity в методе getData. Там много кода и логики, и все обернуто в RxJava. Я опишу только важные моменты.

Сначала мы создаем запрос:

        // создаем запрос на получение записей
        ApiRequest<OperationHistory> request = new OperationHistory.Request.Builder()
                .setRecords(100) // размер страницы - 100 записей
                .setStartRecord(next) // грузить записи страницы, которая начинается с next
                .setFrom(new DateTime(2017, 5, 1, 0, 0)) // грузить записи начиная с 1 мая 2017
                .create();

Данные по операциям выдаются постранично. Мы указываем размер страницы 100.
В setFrom передаем дату 1 мая 2017 года. Сервер вернет операции, выполненные позже этой даты.

Параметр next содержит номер последней загруженной записи. Он нужен, чтобы указать какую страницу данных мы хотим получить.

Т.е. сначала next = 0, и мы получим от сервера первые 100 записей и новое значение для next, он станет равен 100.
Запрос с next = 100 вернет нам вторые 100 записей, а next станет равным 200. И так далее.
Когда сервер вернет нам пустой next, это будет означать, что данных больше нет.

 

Далее мы выполняем этот запрос с помощью apiClient:

OperationHistory operationHistory = apiClient.execute(request);

В результате запроса мы получим объект OperationHistory из которого мы можем достать все операции методом operations.

 

Для каждой операции мы создаем и выполняем отдельный запрос на детальную информацию о ней

ApiRequest<OperationDetails> request = new OperationDetails.Request(operation.operationId);
OperationDetails operationDetails = apiClient.execute(request);

 

А из OperationDetails мы уже можем достать объект Operation, который содержит детальную информацию по операции

Operation operation = operationDetails.operation;

 

В примере я вывожу на экран дату, короткое описание и тип каждой операции.

 

 

Невалидный токен

Если по каким-то причинам токен больше не действителен, то при попытке запроса вы в Observer.onError в методе getData получите ошибку:
com.yandex.money.api.exceptions.InvalidTokenException: Bearer error="invalid_token"

В этом случае надо снова проходить авторизацию.

 


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня




Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

Социальные сети

 

В канале я публикую ссылки на интересные и полезные статьи по Android

В чате можно обсудить вопросы и проблемы, возникающие при разработке



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal