Пару недель назад в одном из проектов возник вопрос об интеграции CRM с API Банка Тинькофф. В частности, речь шла о получении выписки по банковскому счету.
В активе имелось:
- openapi.tinkoff.ru
- Телефон службы поддержки (учитывая занятость отдела техподдержки — дело спасения утопающего, дело рук самого утопающего).
- Нагугленный документ: 24386_policy.pdf (с русским буквами внутри, с занятными выражениями, оборотами колдовскими, малопригодная но, все же вещь...)
В ходе гуглежа были также найдены отзывы о том, что настройка API банка Тинькофф дело весьма занимательное и нетривиальное (см. статью на banki.ru «API Тинькофф — мы слишком глупы для этого»).
Да, пришлось малость повозиться, поэтому, дабы сэкономить время другим товарищам по цеху, была написана данная статья.
Отмечу, что API банка Тинькофф использует Oauth 2.0 для авторизации.
Зачем же нужен openapi.tinkoff.ru?
- для теста (см. ниже);
- для того чтобы догадаться что чего и как; прямого тутора там нет; работаем на уровне интуиции!...
Приступим. В разделе «SSO Авторизация», кликнем на «how/Hide», и затем /secure/token#refresh-token («Выдача токена по рефреш токену»), в качестве параметра выбираем grant_type, далее в поле refresh_token (его можно получить в Личном кабинете пользователя). Жмем кнопку «Try it out!» Результатом этих действий является получение такой важной вещи как access_token (т.е. openapi.tinkoff.ru демонстрирует возможность ее получения).
Далее смотрим раздел «Счета и платежи», кликаем /partner/company/{INN}/excerpt («Получение выписки»). Изучаем, какие параметры необходимы для того чтобы ее заполучить: Authorization, INN, accountNumber, from, till.
Authorization — догадываемся, что Authorization это не что иное как access_token, который был получен нами в разделе «SSO Авторизация»;
INN — ИНН организации для которой настраиваем API;
from — с какого дня (период выписки);
till — по какой день (период выписки).
Таким образом (смотрим матчасть Oauth 2.0), получение данных выписки происходит в два этапа — сначала получаем access_token, затем имея на руках access_token, получаем данные этой самой выписки. Отлично. Алгоритм ясен, пишем код (параметры доступа в коде значения для $user, $pass, $refresh_token, $inn, $accountNumber — в приведенном ниже коде изменены, по понятным причинам).
Создадим следующие файлы:
- Первый файл настроек — StartSettings.php
- Второй файл стартовый — Start.php
- Третий файл постинга/парсинга данных в/из API — TinkoffInsertData.php; используем CURL(php).
- Пустой дамп базы данных, куда можно заливать данные ваших выписок: bank.sql; база MySQL (данные в базу направляем через PDO).
Итак, смотрим код и комментарии к нему!
Файл настроек — StartSettings.php:
$host = '127.0.0.1';
$db = 'bank';
$user = 'root';
$pass = '';
$charset = 'utf8';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $pass, $opt);
$user="IKu0jn98kllkI90kklii"; //20 символов
$pass="ds4234SDFsdfsdijoijslkkdjfoIOi"; //30 символов
$refresh_token='dsfh345kljlkjsdf098sdfkljklj098sdfkklKKLjhjihiKL90909llkrre5345dfFDDFretertERTERETfdgd==';// 88 символов
$inn = '750151513135';
$accountNumber = '40802810300000121212';//20 символов
$from_year = '1980';
$from_month = '01';
$from_day = '01';
$till_year = date('Y');
$till_month = date('m');
$till_day = date('d');
Файл стартовый — Start.php:
session_start();
error_reporting(E_ALL);
include 'StartSettings.php';
include 'TinkoffInsertData.php';
TinkoffInsertData($user,$pass,$refresh_token, $inn, $accountNumber, $from_year, $from_month, $from_day, $till_year, $till_month, $till_day, $pdo);
$stmt = $pdo->prepare("INSERT INTO `bank`.`dateofwork` (dateofwork) VALUES (NOW())");
$stmt->execute();
Файл постинга/парсинга данных в/из API — TinkoffInsertData.php:
function TinkoffInsertData($user,$pass,$refresh_token, $inn, $accountNumber, $from_year, $from_month, $from_day, $till_year, $till_month, $till_day, $pdo){
//Первый этап - поход джедаев за access_token
$from_date = $from_year."-".$from_month."-".$from_day.'%2B03%3A00%3A00';
$till_date = $till_year."-".$till_month."-".$till_day.'%2B03%3A00%3A00';
$params=['grant_type'=>'refresh_token',
'refresh_token'=>$refresh_token
];
$headers = [
'POST /secure/token HTTP/1.1',
'Content-Type: application/x-www-form-urlencoded'
];
$curlURL='https://sso.tinkoff.ru/secure/token';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$curlURL);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $user . ":" . $pass);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
$curl_res = curl_exec($ch);
if($curl_res) {
$server_output = json_decode($curl_res);
}
//Считываем access_token - он нужен для реализации 2 этапа
$access_token_pos_start = strpos ($curl_res, 'access_token', 0);
$access_token_pos_start = $access_token_pos_start + 15;
$token_type_pos_start = strpos ($curl_res, "token_type", 0);
$access_token = mb_substr($curl_res, $access_token_pos_start, ($token_type_pos_start-$access_token_pos_start-3));
//Ура!.... мы сделали это.....
//По желанию, можете расскомментировать данный sleep, но в принципе работает и без него
//sleep(1);
//Второй этап - поход джедаев за данными
$params=[
'Authorization'=>$access_token,
'INN'=>$inn,
'accountNumber'=>$accountNumber
];
$headers = [
'Authorization: Bearer '.$access_token
];
$curlURL='https://sme-partner.tinkoff.ru/api/v1/partner/company/'.$inn.'/excerpt?accountNumber='.$accountNumber.'&from='.$from_date.'&till='.$till_date;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$curlURL);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $user . ":" . $pass);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
$curl_res = curl_exec($ch);
if($curl_res) {
$server_output = json_decode($curl_res);
}
$IE_Edge_pos_start = strpos ($curl_res, 'IE=Edge', 0);
$IE_Edge_pos_start = $IE_Edge_pos_start + 7;
$tinkoff_json = mb_substr($curl_res, $IE_Edge_pos_start);
$tinkoff_json = trim($tinkoff_json);
$tinkoff_json = json_decode($tinkoff_json);
//а тот ли счет мы считываем, собсно ;)
foreach ($tinkoff_json as $k=>$v){
if($k=='accountNumber'){
if(!($v==$accountNumber)) die('not that accountNumber');
}
}
//$tinkoff_array - записываем данные из json в массив
foreach ($tinkoff_json as $k=>$v){
if($k=='operation'){
$i=0;
foreach ($v as $t=>$s){
foreach ($s as $e=>$f){
$tinkoff_array[$i][$e]=$f;
}
$i++;
}
}
}
//заливаем данные из $tinkoff_array в базу данных
for ($i=0;$i<count($tinkoff_array);$i++){
$temp_id = $pdo->query("SELECT count(*) FROM `justtin`.`tinkoff` WHERE id=".$tinkoff_array[$i]['id'].";")->fetchColumn();
if ($temp_id==0){
if (Get_highly_likely_is_number_bill($tinkoff_array[$i]['paymentPurpose'])!=""){
$stmt = $pdo->prepare("INSERT INTO `justtin`.`tinkoff` (id, date, amount, drawDate, payerName, payerInn, payerAccount, payerCorrAccount, payerBic, payerBank, chargeDate, recipient, recipientInn, recipientAccount, recipientCorrAccount, recipientBic, recipientBank, operationType, uin, paymentPurpose, creatorStatus, payerKpp, executionOrder, date_of_save) VALUES (:id, :date, :amount, :drawDate, :payerName, :payerInn, :payerAccount, :payerCorrAccount, :payerBic, :payerBank, :chargeDate, :recipient, :recipientInn, :recipientAccount, :recipientCorrAccount, :recipientBic, :recipientBank, :operationType, :uin, :paymentPurpose, :creatorStatus, :payerKpp, :executionOrder, NOW())");
$stmt->bindParam(':id', $tinkoff_array[$i]['id']);
$stmt->bindParam(':date', $tinkoff_array[$i]['date']);
$stmt->bindParam(':amount', $tinkoff_array[$i]['amount']);
$stmt->bindParam(':drawDate', $tinkoff_array[$i]['drawDate']);
$stmt->bindParam(':payerName', $tinkoff_array[$i]['payerName']);
$stmt->bindParam(':payerInn', $tinkoff_array[$i]['payerInn']);
$stmt->bindParam(':payerAccount', $tinkoff_array[$i]['payerAccount']);
$stmt->bindParam(':payerCorrAccount', $tinkoff_array[$i]['payerCorrAccount']);
$stmt->bindParam(':payerBic', $tinkoff_array[$i]['payerBic']);
$stmt->bindParam(':payerBank', $tinkoff_array[$i]['payerBank']);
$stmt->bindParam(':chargeDate', $tinkoff_array[$i]['chargeDate']);
$stmt->bindParam(':recipient', $tinkoff_array[$i]['recipient']);
$stmt->bindParam(':recipientInn', $tinkoff_array[$i]['recipientInn']);
$stmt->bindParam(':recipientAccount', $tinkoff_array[$i]['recipientAccount']);
$stmt->bindParam(':recipientCorrAccount', $tinkoff_array[$i]['recipientCorrAccount']);
$stmt->bindParam(':recipientBic', $tinkoff_array[$i]['recipientBic']);
$stmt->bindParam(':recipientBank', $tinkoff_array[$i]['recipientBank']);
$stmt->bindParam(':operationType', $tinkoff_array[$i]['operationType']);
$stmt->bindParam(':uin', $tinkoff_array[$i]['uin']);
$stmt->bindParam(':paymentPurpose', $tinkoff_array[$i]['paymentPurpose']);
$stmt->bindParam(':creatorStatus', $tinkoff_array[$i]['creatorStatus']);
$stmt->bindParam(':payerKpp', $tinkoff_array[$i]['payerKpp']);
$stmt->bindParam(':executionOrder', $tinkoff_array[$i]['executionOrder']);
$stmt->execute();
}
}
}
}
Читателям: надеюсь, данный материал поможет в монетизации ваших веб-сервисов и сервисов ваших заказчиков. Да прибудет с вами сила!
Ребятам из техподдержки Банка Тинькофф: надеюсь, данная статья снизит нагрузку на вас! Удачи!
Автор: optimusqp