BANKS FAQ : Импорт данных из внешних провайдеров

В данном разделе описано как работает подсистема импорта данных из внешних провайдеров через CASHOFF API. Описание содержит следующие составляющие:

  • добавление связи CASHOFF с провайдером через профиль
  • авторизация профиля в провайдере
  • процесс импорта данных
  • получение загруженных данных из CASHOFF

Цикл работы с профилями при импорте данных

Глобально цикл состоит из следующих шагов:

  1. Добавление нового профиля. При добавлении указывается пользователь и провайдер.
  2. Запуск обновления данных. В рамках обновления данных из ЛК провайдера данные будут импортированы в CASHOFF.
  3. Авторизация в ЛК провайдера. Это первый, обязательный, этап обновления. На этом шаге либо клиенту нужно будет предоставить данные для авторизации (логин/пароль/смс и т.п.), либо CASHOFF использует сохраненные у него данные авторизации или сессию для восстановления подключения к провайдеру.
  4. Импорт данных. Выполняется после успешной авторизации

CASHOFF позволяет выполнить шаги 1-3 двумя разными способами: полностью через серверное API или через готовые визуальные модули.

После успешного добавления и первичного импорта данных можно повторно обновлять профиль выполняя шаги 2-4. 

Сам процесс авторизации и импорта данных происходит асинхронно: соответствующие API методы только запускают выполнение действия, взаимодействие с провайдером же происходит в фоновом режиме.

Результат выполнения действий отслеживается через статус профиля (поле status), а прогресс через поля update_current_stage и update_progress. Основной способ отслеживания - нотификаций, в частности событие profile.updated, которое описано ниже в разделе "Уведомление о изменении профиля".

Загруженные на шаге 4 данные доступны на постоянной основе и могут в любое время получены через API. К примеру для списка продуктов это запрос GET /accounts, а операций - GET /transactions. Запрашивать можно и в момент импорта, однако данные в таком случае будут только частично обновлены.

Запуск импорта через серверное API

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

Запуск импорта через визуальные модули

CASHOFF предоставляет готовый визуальный модуль profile-update, через который клиент может выбрать провайдера для подключения, авторизоваться и наблюдать процесс импорта данных. Приложению остается только забрать данные (шаг 4). Подробнее о использовании визуальных модулей написано в разделе Встраиваемые UI модули.

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



Рисунок 1. Общий цикл импорта данных


Так же его можно использовать исключительно для авторизации: показывать модуль при статусе профиля updating. Такой подход позволяет избавить приложение от необходимости работать с авторизационными данными клиента от провайдеров.

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

В итоге приложению остается только отслеживать завершение обновления профиля (серверными нотификациями или с помощью сообщений на клиенте) и получить данные из CASHOFF. Повторные обновления можно производить так же при помощью этого модуля.

Добавление нового профиля

Добавление профиля происходит запросом POST /profiles/add, в котором нужно указать идентификатор провайдера:

Запрос
{
    "provider_key": "sber"
}

Запрос в таком виде только создаст новый профиль, но не выполнит шаги 2 и 3.

Эти действия выполняются отдельными запросами, POST /profiles/{profile_id}/update и POST /profiles/{profile_id}/session/auth соответственно, но их можно выполнить и разом при создании:

Запрос
{
    "provider_key": "sber",
	"start_update": true,
    "auth_data": {
        "login": "VApTr56Ui9",
        "password": "8Apq0Ohy1"
    }
}

В данном запросе атрибут start_update запустит обновление (шаг 2), а auth_data предоставит данные для первого шага авторизации (шаг 3). 

Пример ответа:

Ответ
{
    "profile": {
        "id": 1291399,
        "user_id": 1330891,
        "provider": {
            "id": 72,
            "key": "sber",
            "name": "Сбербанк",
            "type": "bank",
            "logo": "https://cashoff.ru/static/build/cashoff/v-7c7f6a8a477c1b3e3d20a0ca10147158/img/logo/fi/sber_36.svg"
        },
        "session": {
            "key": "ac1db988825748b595526852e02c2712",
            "status": "ok"
        },
        "status": "updating",
        "created": "2019-03-28T08:52:23Z",
        "changed": "2019-03-28T08:52:23Z"
    },
	"update_status": "ok"
}

Переданные при создании профиля данные авторизации (auth_data) используются только если одновременно запускается обновление (start_update). Если обновление не запускается, то будет сохранен только логин (для контроля уникальности профиля у пользователя), а пароль не перенесется к моменту следующего запуска обновления.

Если на первом шаге нет данных для ввода (например "пустой" шаг, который будет описан далее), то для выполнения авторизации нужно указать пустой auth_data, а не пропускать его совсем: без него запрос будет считаться просто запуском обновления, без выполнения шага авторизации.

CASHOFF не позволяет одному и тому же пользователю добавлять несколько одинаковых профилей (профили с одинаковым провайдером и логином). При повторном добавлении будет возвращена ошибка profile_exists, а в атрибуте extra.existing_profile_id можно будет найти идентификатор существующего дублирующего профиля. Подобная же ошибка будет при запросе POST /profiles/{profile_id}/session/auth, если изначально запрос создания профиля не включал логин, а тут он пришел и нашелся дублирующий профиль.

Запуск обновления профиля

Обновление запускается с помощью запроса POST /profiles/{profile_id}/update. Ответ придет с полем status, в котором будет указано что обновление либо успешно запущено (ok), либо код причины невозможности запуска.

Обновление состоит из двух ключевых фаз: 

  • Авторизация. Сначала идет попытка подключения по сохраненным данным (есть еще активная сессия). Если это не получается, то запускается сам процесс авторизации
  • Импорт данных. Данная фаза возможна только после успешной авторизации. 

Авторизация в ЛК провайдера

В процессе авторизации CASHOFF с помощью предоставляемых клиентом данных пытается авторизоваться в ЛК провайдера. В авторизации есть ряд ключевых моментов, которые нужно учитывать:

  1. Последовательность шагов авторизации не фиксирована: многие ЛК поддерживают вход как с смс, так и без, могут запрашивать проверочные вопросы, а так же выдавать дополнительные шаги в зависимости от введенных данных. Как результат набор шагов и их количество могут меняться в зависимости от пользователя, даже в рамках одного профиля.
  2. Пользователь может ошибаться при вводе данных авторизации, по этому каждый шаг может завершиться ошибкой введенных данных (например не правильный пароль). Такую ошибку необходимо показать пользователю и для продолжения обновления он должен будет повторить ввод данных текущего шага.

С учетом этих моментов общая схема авторизации выглядит следующим образом:

  1. CASHOFF проверяет статус сохраненной у него сессии в ЛК провайдера.
    1. Сессия "жива" (по ней можно получить данные): авторизация завершена, переход к шагу 6.
    2. Сессия не "жива": начинается непосредственно процесс авторизации.
  2. CASHOFF определяет текущий шаг авторизации и указывает в профиле какой шаг нужно выполнить сейчас (session.auth_step).
  3. Приложение показывает пользователю форму из session.auth_step.
  4. Пользователь вводит данные в форму, приложение отправляет форму в CASHOFF.
  5. CASHOFF применяет данные для авторизации в ЛК. Далее может быть один из следующих исходов:
    1. Шаг выполнен успешно, больше шагов нет: авторизация завершена, переход к шагу 6.
    2. Шаг выполнен успешно, но есть следующий: профиль получает новую форму (session.auth_step) и с ней повторяет шаги 3-5.
    3. Шаг выполнен неуспешно: форма (session.auth_step) остается та же, приложение показывает пользователю ошибку и он повторяет ввод данных (шаг 4).
  6. Авторизация завершена, начинается импорт данных.


Рисунок 2. Проверка сессии и запуск авторизации


Таким образом авторизация представляет собой цикл из шагов 2-5, который предваряется проверкой статуса имеющейся сессии и завершается переходом в импорт данных.

Данные на шаге 5 передаются в CASHOFF с помощью запроса POST /profiles/{profile_id}/session/auth и должны соответствовать текущему шагу (session.auth_step):

Запрос
{
    "auth_data": {
        "sms": "1234"
    }
}

Т.к. авторизация является частью процесса обновления, то каждый раз при отправке данных профиль будет переходить в статус updating, взаимодействовать фоново с провайдером и по результату переходить в какой-то из статусов: auth, error, ok

Завершение фазы авторизации можно отследить по тому, что update_current_stage <> auth.

В CASHOFF есть ограничение в 15 минут на выполнение шага 4: если с момента установки текущего шага авторизации не придет в течении 15 минут  запрос с данным для него, то процесс авторизации (вместе с обновлением) будет сброшен и обновление нужно будет начинать заново. Любая отправка данных, даже не позволившая пройти шаг, приведет к обновлению таймера. По этому суммарно время авторизации не ограничено этими 15 минутами - это ограничение только на попытку (на стороне CASHOFF, провайдеры всё равно могут накладывать свои ограничения).

Описания шагов авторизации

Каждый шаг авторизации провайдера представляет собой форму с набором полей. Типовая форма первого шага выглядит следующим образом:

Запрос
{
	"key": "initial",
	"fields": [
		{
			"key": "login",
			"type": "text",
			"label": "Логин"
		},
		{
			"key": "password",
			"type": "password",
			"label": "Пароль"
		}
	]
}

У данной формы ключ initial, и в ней содержится поля: login и password. По ней необходимо вывести пользователю форму с двумя полями, "Логин" и "Пароль". Поля формы с type=password должны маскировать вводимые в них данные.

Если в форме указан атрибут help, то его содержимое нужно вывести пользователю над полями - это вспомогательный текст содержащий 1-2 предложения, который поясняет особенности ввода данных на данной форме.

Если заполнен атрибут session.status_message, то его текст нужно вывести в качестве ошибки формы.

Результат ввода по этой форме, будучи отправленным в cashoff, выглядит следующим образом:

Запрос
{
    "auth_data": {
        "login": "1234",
        "password": "qwerty"
    }
}

Шаг авторизации с выбором формы

В качестве шага авторизации session.auth_step может быть указана не одна форма, а несколько: это означает что пользователь может выбрать с помощью каких данных авторизоваться. При этом разные формы могут привести к разным последующим шагам. Вот пример шага из двух форм на выбор:

Запрос
[
  {
    "key": "credentials",
    "fields": [
      {
        "key": "login",
        "type": "text",
        "label": "Логин"
      },
      {
        "key": "password",
        "type": "password",
        "label": "Пароль"
      }
    ]
  },
  {
    "key": "phone",
    "fields": [
      {
        "key": "phone",
        "type": "text",
        "label": "Телефон"
      }
    ]
  }
]

В примере пользователь может либо ввести логин и пароль, либо ввести номер телефона. Нужно предоставить пользователю выбор из этих двух форм, дать ему заполнить одну из них и отправить POST /profiles/{profile_id}/session/auth с обязательным указанием в атрибуте form_key какая форма выбрана:

Запрос
{
  "form_key": "phone",
  "auth_data": {
    "phone": "+79991234567"
  }
}

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


Рисунок 3. Login & Password Authorization Flow — общий сценарий

Авторизация с помощью QR-кода

В CASHOFF у ряда провайдеров есть возможность авторизации с помощью сканирования  QR-кода приложением провайдера и подтверждения авторизации в нём. В таком случае на этапе сканирования придёт следующая форма:

Запрос
{
  "key": "check_qr_code",
  "help": "Отсканируйте QR-код в Вашем приложении банка.",
  "qr_code": "gpbapp://auth/qr?code=AQICGySDjnmLpaiVQnADiWPO1D1B7e4g5sPzRRDg7F8OPERZy5Rbtd1H5dW2elmvz4aoUd6U5GhmskyiIIx2QQFBKmoNj3nCokDMFvyOuZf+vSZU0xo91slLmoY604oJ7Oi78aNO0TAUGLYyykZcEcNfc&info=aSzAr0WCcr7Ftzl2quVSY9mF",
  "qr_code_usage_hint": "inapp_scan_image",
  "qr_expiration_time": "2026-05-27T16:58:14Z",
  "fields": []
}

В ней пустой список полей (т.к. вводить ничего не нужно), но указан атрибут qr_code - по нему нужно показать пользователю QR-код. В данном случае нужно показать пользователю форму с текстом "Отсканируйте QR-код в Вашем приложении банка.", под ней QR-код и далее кнопка перехода на следующий шаг авторизации.

Система не проверяет в фоне факт подтверждения авторизации в приложении провайдера: пользователю нужно самостоятельно нажать кнопку перехода на следующий шаг (а приложению отправить POST /profiles/{profile_id}/session/auth) после того, как он авторизацию подтвердил. Т.к. на форме нет полей для ввода, то данные авторизации в запросе будут пустым объектом:

Запрос
{
  "auth_data": {}
}

После этого CASHOFF проверит прошла ли авторизация и пойдёт дальше если это так. Если авторизация не прошла и текущий QR-код истёк, то вернётся форма с новым QR-кодом - она должна заменить старую.

Опционально с QR-кодом может прийти так же срок его действия (qr_expiration_time) и подсказка об способе сканирования (qr_code_usage_hint). Последняя поможет если авторизация происходит уже на телефоне и QR-код просканировать напрямую не получится.

QR-код на первом шаге

Первый шаг авторизации всегда статичен, по этому на нём не может прийти персонального QR-кода. Если авторизация начинается со сканирования, то в начале будет промежуточный "пустой" шаг с пояснением что будет дальше, например:

Запрос
{
  "key": "qr_code",
  "name": "Вход по QR-коду",
  "help": "Для входа потребуется отсканировать QR-код внутри приложения банка.",
  "qr_code_usage_hint": "open_url",
  "fields": []
}

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

Важно отметить что это выбор единоразовый в рамках данной авторизации т.к. нет возможности перейти на шаг назад - пользователь далее сможет продолжить только с QR-кодом. Пользователю нужно заново начать авторизацию что бы иметь возможность выбрать другой сценарий авторизации. Что бы начать заново нужно сделать сброс запросом POST /profiles/{profile_id}/reset.

В данном примере пользователь может зайти с помощью номера телефона или по QR-коду:

Запрос
[
  {
    "key": "phone",
    "name": "Вход по номеру телефона",
    "help": "Введите номер телефона в формате +79999999999.",
    "fields": [
      {
        "key": "login",
        "type": "text",
        "label": "Номер Телефона",
      }
    ]
  },
  {
    "key": "qr_code",
    "name": "Вход по QR-коду",
    "help": "Для входа потребуется отсканировать QR-код внутри приложения банка.",
    "qr_code_usage_hint": "open_url",
    "fields": []
  }
]

Если пользователь выберет форму qr_code, то на следующем шаге он получит QR-код для сканирования. 

В такие формы так же добавляется qr_code_usage_hint: он сигнализирует какой qr_code_usage_hint будет на следующем шаге. Если текущий вариант не подходит, то форму для пользователя можно скрыть и оставить только подходящую (в данном случае ввод номера телефона).

Включение авторизации по QR-коду

Для обратной совместимости данный способ авторизации сделан опциональным. Что бы включить его нужно сделать запрос POST /settings/profiles/optional-features и указать там qr_auth: true. Без данного флага формы авторизации по qr будут исключаться из выдачи там, где они опциональны.


Рисунок 4. Пример возможного варианта авторизации через QR-код


Импорт данных

После успешной авторизации обновление переходит к импорту данных. Сигналом окончания обновления будет переход профиля в статус ok (успешное завершение) или fatal (обновление прервалось ошибкой). Во втором случае текст ошибки будет доступен в поле session.status_message - это готовое сообщение для пользователя.

Набор данных зависит от типа провайдера, подробнее о списке загружаемых данных в зависимости от типа провайдера написано в разделе Провайдеры.

В CASHOFF обновление данных выполняется где возможно инкрементно: при каждом обновлении запрашиваются только данные за те даты, за которые они еще не загружены. 

Указание выполняемых при импорте данных действий

При запуске обновления можно указать, какие данные нужно импортировать - за это отвечает атрибут update_stages (есть как в методе запуска обновления, так и в методе создания профиля). В нём перечисляются шаги, которые нужно выполнить в рамках обновления, а так же можно на некоторых шагах затребовать выполнение дополнительных действий. Атрибут может заполняться в двух форматах: сокращенном (список идентификаторов шагов) и развернутом (для каждого шага можно указать доп. действия). Пример сокращенного:

Запрос
["auth", "accounts"]

(запрошена авторизация и загрузка списка продуктов). Пример развернутого:

Запрос
{
	"auth": {},
	"accounts": {
		"payment_rub": true
	}
}

(то же самое, только для продуктов запрошено определение того, с какого продукта можно совершать рублевые платежи через CASHOFF). 

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

Запрос
["auth"]

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

Если атрибут не заполнен, то шаги по умолчанию проставляются согласно типу провайдера.

На каком конкретно этапе обновления сейчас находится профиль можно узнать через поле update_current_stage. К примеру в случае авторизации там будет auth, а в случае загрузки списка продуктов - accounts.

Ошибки в процессе обновления данных

Импорт данных может завершиться ошибкой, вызванной как парсером, так и провайдером. В таких случаях профиль получает статус status=error, а текст ошибки будет в поле session.status_message; код причины ошибки можно найти в атрибуте last_attempt_error.

При ошибке обновление прерывается. Далее можно либо сбросить текущий статус (POST /profiles/{profile_id}/reset), либо запустить обновление заново (POST /profiles/{profile_id}/update) (по сути это сброс + запуск).

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

Уведомления об изменении профиля

В CASHOFF предусмотрено событие profile.updated, уведомления по которому отправляется при изменении профиля. Изменение включают в себя изменение профиля (статуса и текущего этапа обновления), состояния сессии

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

В теле уведомления profile.updated приходит полная информация по профилю, аналогичная выдаче запроса GET /profiles/{profile_id}.

Ниже представлены примеры уведомлений по различным этапам импорта данных

Требуется ввод данных авторизации (смс)

{
  "event""profile.updated",
  "data": {
    "id"1289652,
    "user_id"1330891,
    "provider": {
      "id"123,
      "key""pyaterochka",
      "name""Пятерочка",
      "type""retail_store",
      "logo""https://cashoff.ru/static/build/cashoff/v-7c7f6a8a477c1b3e3d20a0ca10147158/img/logo/fi/pyaterochka_36.svg"
    },
    "session": {
      "key""4001dc087366468a961022bdb8542ffb",
      "status""ok",
      "auth_step": {
        "key""2",
        "help""На Ваш мобильный телефон выслано SMS-сообщение с паролем для входа.Пожалуйста, введите полученный пароль.",
        "fields": [
          {
            "key""sms",
            "type""text",
            "label""Смс-Код"
          }
        ]
      }
    },
    "status""auth",
    "created""2019-03-27T15:15:28Z",
    "changed""2019-03-27T15:22:01Z"
  }
}

Обновление успешно завершено

{
  "event""profile.updated",
  "data": {
    "id"1289652,
    "user_id"1330891,
    "provider": {
      "id"123,
      "key""pyaterochka",
      "name""Пятерочка",
      "type""retail_store",
      "logo""https://cashoff.ru/static/build/cashoff/v-7c7f6a8a477c1b3e3d20a0ca10147158/img/logo/fi/pyaterochka_36.svg"
    },
    "session": {
      "key""4001dc087366468a961022bdb8542ffb",
      "status""ok"
    },
    "status""ok",
    "created""2019-03-27T15:15:28Z",
    "changed""2019-03-27T15:24:46Z",
    "accounts_updated_at""2019-03-27T15:24:46Z",
    "update_done_at""2019-03-27T15:24:46Z",
    "update_started_at""2019-03-27T15:22:49Z"
  }
}

Неверные данные авторизации

{
  "event""profile.updated",
  "data": {
    "id"1289757,
    "user_id"1330891,
    "provider": {
      "id"123,
      "key""pyaterochka",
      "name""Пятерочка",
      "type""retail_store",
      "logo""https://cashoff.ru/static/build/cashoff/v-7c7f6a8a477c1b3e3d20a0ca10147158/img/logo/fi/pyaterochka_36.svg"
    },
    "session": {
      "key""f1f4a828b6cb403d9c23367785cd9e18",
      "status""error",
      "status_message""Неверный логин и/или пароль",
      "auth_step": {
        "key""1",
        "help""Введите номер карты лояльности или номер телефона и пароль",
        "fields": [
          {
            "key""login",
            "type""text",
            "label""Номер Карты Или Телефона"
          },
          {
            "key""password",
            "type""password",
            "label""Пароль"
          }
        ]
      }
    },
    "status""auth",
    "created""2019-03-27T15:51:59Z",
    "changed""2019-03-27T15:53:37Z"
  }
}

Ошибка обновления профиля

{
  "event""profile.updated",
  "data": {
    "id"1291399,
    "user_id"1330891,
    "provider": {
        "id"72,
        "key""mkb",
        "name""Московский Кредитный Банк",
        "type""bank",
        "logo""https://cashoff.ru/static/build/cashoff/v-7c7f6a8a477c1b3e3d20a0ca10147158/img/logo/fi/mkb_36.svg"
    },
    "session": {
        "key""ac1db988825748b595526852e02c2712",
        "status""error",
        "status_message""Произошла ошибка при взаимодействии с системой \"Московский Кредитный Банк\". Попробуйте повторить попытку позже."
    },
    "status""error",
    "created""2019-03-28T08:52:23Z",
    "changed""2019-03-28T08:53:46Z",
    "update_started_at""2019-03-28T08:53:42Z"
}