При попытке связать 1с с PayPal пришлось столкнуться с несколькими нюансами, которыми и хотелось бы поделиться в этой статье. В принципе через API PayPal с аккаунтом можно делать вообще практически все что угодно – не только получать информацию, но при желании даже управлять перечислениями средств, возвратами и т.п. У меня была более простая задача – получить информацию об оплатах клиентов в 1С.
В первую очередь идем за официальной документацией сюда https://developer.paypal.com/docs/api/overview/
После прочтения документации сложилась общая картина взаимодействия с API по принципам похожая на работу с мессенджером Slack – считаю его самым удобным для разработчиков. (если интересно, то пишите комментарии чтобы появился стимул для статьм как работать из 1С со Slack и даже при желании создать там своего бота).
Связь с API PayPal настраивал через HTTP запросы. Вкратце – нужно включить доступ по API и настроить в терминах PayPal что-то типа Приложение, у которого взять нужные для связи ClientID и ClientSecret, далее через спец. запросы получать временный токен доступа для работы и уже используя полученный токен доступа делать запросы. Как включить работу через API см. на картинках.
Получаем то, ради чего мы включали API
Далее желательно проверить работу полученной связки через Postman, инструкция https://developer.paypal.com/docs/api/get-an-access-token-postman/ нам в помощь. У меня долго не получалось получить временный Токен доступа, то ли версия Postman была другая, то ли документация устаревшая, но в итоге если задать получение токена вручную
и вставить также потом вручную токен в ключ Authorization обязательно после слова Bearer c пробелом, то запросы и ответы у меня в Postman заработали.
Далее уже дело техники и кода в самом 1C. В первую очередь перед запросами получаем программно токен доступа.
&НаСервере
Процедура ПолучитьТокенДоступа()
Host = Объект.ДанныеЗапросов.Host;
GetTokenURL = Объект.ДанныеЗапросов.GetTokenURL;
ClientID = Объект.ДанныеЗапросов.ClientID;
ClientSecret = Объект.ДанныеЗапросов.ClientSecret;
StrInput = ClientID+":"+ClientSecret;
paramvalue = СтрокаBase64БезBOM(StrInput);
HTTPЗапрос = Новый HTTPЗапрос;
HTTPЗапрос.АдресРесурса = GetTokenURL;
HTTPЗапрос.Заголовки.Вставить("Authorization", "Basic "+paramvalue);
HTTPЗапрос.Заголовки.Вставить("Accept", "application/json");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");
HTTPЗапрос.Заголовки.Вставить("Accept-Language","en_US");
HTTPЗапрос.Заголовки.Вставить("Cache-Control", "no-cache");
HTTPЗапрос.УстановитьТелоИзСтроки("grant_type=client_credentials");
Прокси = Новый ИнтернетПрокси(Истина);
Соединение = Новый HTTPСоединение(Host,,,,Прокси,10,Новый ЗащищенноеСоединениеOpenSSL);
ЧтениеJSON = Новый ЧтениеJSON;
Попытка
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос); // post запрос
ЧтениеJSON.УстановитьСтроку(HTTPОтвет.ПолучитьТелоКакСтроку());
Данные = ПрочитатьJSON(ЧтениеJSON,Ложь);
Объект.ДанныеЗапросов.access_token = Данные.access_token;
Объект.ДанныеЗапросов.app_id = СтрЗаменить(Данные.app_id, "APP-","");
Если HTTPОтвет.КодСостояния > 299 Тогда
ТекстОшибки = "HTTPОтвет.КодСостояния|" + HTTPОтвет.КодСостояния + "|Токен доступа в API PayPal не получен";
ЗаписьЖурналаРегистрации("PayPal", УровеньЖурналаРегистрации.Предупреждение,,,ТекстОшибки);
ВызватьИсключение ТекстОшибки;
КонецЕсли;
Исключение
ТекстОшибки = "ОтправкаPayPal|"+ОписаниеОшибки();
ЗаписьЖурналаРегистрации("PayPal",УровеньЖурналаРегистрации.Ошибка,,,ТекстОшибки);
КонецПопытки;
КонецПроцедуры // ПолучитьТокенДоступа()
Здесь ничего сложного, кроме того, что строку ClientD+ClientSecret перед передачей надо обернуть в Base64 – об этом в официальной документации не было ни слова или я плохо искал или это считается по-умолчанию уж не знаю, но так как 1с на зарубежных форумах, к сожалению ((( и рассчитываю что только пока вообще не представлен, то помогли подсказки с других форумов, в частности спасибо ребятам по PHP.
Ну может теперь можно уже посылать любые нужные нам запросы через API…?!…, но и здесь есть подводные камни. Первое: если ответ API превышает более 100-500 строк (где-то в дебрях настроек PayPal выставляется– не успел нарезать скриншоты, но найти несложно), то ответ разбивается на страницы по 100-500 ответов и находясь в контексте 1С &НаСервере – ни в какую не хотели страницы "перелистываться". Пришлось делать цикл &НаКлиенте и собирать ответы с сервера.
&НаКлиенте
Процедура ВызватьApiPayPal(Команда)
Если ОплатыСписком.Количество() > 0 Тогда
ОплатыСписком.Очистить();
КонецЕсли;
Если ИнтервалОтбораДатаОкончания < ИнтервалОтбораДатаНачала Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "ДатаОкончания не может быть меньше ДатыНачала";
Сообщение.Сообщить();
Возврат;
ИначеЕсли (ИнтервалОтбораДатаОкончания-ИнтервалОтбораДатаНачала)>30*86400 Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Ограничение PayPal API - Интервал запроса не должен превышать 30 дней";
Сообщение.Сообщить();
Возврат;
КонецЕсли;
СтруктураОбмена = Новый Структура("page,total_pages,ХранилищеТранзакций",1,1);
ВызовPayPal(,"transactions",СтруктураОбмена);
СтруктураОбмена.page = СтруктураОбмена.page + 1;
Пока СтруктураОбмена.page <= СтруктураОбмена.total_pages Цикл
ВызовPayPal(,"transactions",СтруктураОбмена);
СтруктураОбмена.page = СтруктураОбмена.page + 1;
КонецЦикла;
Если ТипЗнч(СтруктураОбмена.ХранилищеТранзакций) = Тип("ХранилищеЗначения") Тогда
//!!!!!!!!!! получили данные - уходим на сервер для их окончательного разбора и обработки
ОбработатьДанныеPayPal(СтруктураОбмена.ХранилищеТранзакций);
Если ОбщегоНазначенияКлиентСервер.РежимОтладки() Тогда // показ файла ответа при отладке для визуального анализа данных
ИмяВремФайла = ПолучитьИмяВременногоФайла("txt");
ЗаписьJSON = Новый ЗаписьJSON;
ПарамЗаписи = Новый ПараметрыЗаписиJSON(,Символы.Таб);
ЗаписьJSON.ОткрытьФайл(ИмяВремФайла,,,ПарамЗаписи);
ЗаписатьJSON(ЗаписьJSON, ПолучитьМассивИзХранилищаНаСервере(СтруктураОбмена.ХранилищеТранзакций));
ЗаписьJSON.Закрыть();
Сообщить(ИмяВремФайла);
Оповещение = Новый ОписаниеОповещения("ПослеОткрытияФайла", ЭтотОбъект);
НачатьЗапускПриложения(Оповещение, ИмяВремФайла);
КонецЕсли;
Иначе
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Нет данных с PayPal для обработки в 1с";
Сообщение.Сообщить();
КонецЕсли;
ВыполнитьУсловноеОформлениеФормы();
ЭтаФорма.ОбновитьОтображениеДанных();
КонецПроцедуры
&НаСервере
Процедура ВызовPayPal(Знач номерпопытки = 0, Знач ТипЗапроса = Неопределено, ПарамОбмена)
Если НЕ ЗначениеЗаполнено(Объект.ДанныеЗапросов.access_token) Тогда
ПолучитьТокенДоступа();
Если НЕ ЗначениеЗаполнено(Объект.ДанныеЗапросов.access_token) Тогда
ТекстОшибки = "Hе получен токен доступа";
ВызватьИсключение ТекстОшибки;
КонецЕсли;
КонецЕсли;
start_id = 0;
СтрокаJSON = "";
Host = Объект.ДанныеЗапросов.Host;
Url_Запроса = Объект.ДанныеЗапросов["Url_"+ТипЗапроса];
Если ТипЗапроса="orders" Тогда
// можно делать запрос по конкретному заказу - в примере обрезано.
//Url_Запроса = СтрЗаменить(Url_Запроса, "{order_id}", IDRequest);
ИначеЕсли ТипЗапроса = "payments" Тогда
СтрокаJSON = ПодготовитьСтрокуJSONДляТела(ТипЗапроса,start_id);
ИначеЕсли ТипЗапроса = "transactions" Тогда
РазницаПоясовВСекундах = ТекущаяДатаСеанса()-ТекущаяУниверсальнаяДата(); // не больше 31 дня
РазницаПоясовВЧасах = РазницаПоясовВСекундах/60/60;
Старт = Формат(НачалоДня(ИнтервалОтбораДатаНачала)-РазницаПоясовВСекундах, "ДФ=yyyy-MM-ddTHH:mm:ss")+"-0000";
Конец = Формат(КонецДня(ИнтервалОтбораДатаОкончания)+РазницаПоясовВСекундах+1, "ДФ=yyyy-MM-ddTHH:mm:ss")+"-0000";
Url_Запроса = Url_Запроса + "?start_date="+Старт+"&end_date="+Конец+"&fields=all&transaction_status=S&page_size=500&page="+ПарамОбмена.page;
// можно сделать запрос конкретной транзации - в примере обрезано.
//Url_Запроса = ?(ЗначениеЗаполнено(IDRequest), Url_Запроса+"&transaction_id="+IDRequest,Url_Запроса);
КонецЕсли;
HTTPЗапрос = Новый HTTPЗапрос;
HTTPЗапрос.АдресРесурса = Url_Запроса;
HTTPЗапрос.Заголовки.Вставить("Authorization", "Bearer "+Объект.ДанныеЗапросов.access_token);
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаJSON);
Прокси = Новый ИнтернетПрокси(Истина);
Соединение = Новый HTTPСоединение(Host,,,,Прокси,10,Новый ЗащищенноеСоединениеOpenSSL);
ЧтениеJSON = Новый ЧтениеJSON;
ПарамОбмена.total_pages = 0;
Попытка
HTTPОтвет = Соединение.Получить(HTTPЗапрос); // get запрос
СтрокаОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();
ЧтениеJSON.УстановитьСтроку(СтрокаОтвета);
ДанныеОтвета = ПрочитатьJSON(ЧтениеJSON,Ложь,);
Если ТипЗнч(ДанныеОтвета)=Тип("Структура") И ДанныеОтвета.Свойство("total_pages") И ДанныеОтвета.Свойство("page") Тогда
ЗаполнитьЗначенияСвойств(ПарамОбмена, ДанныеОтвета);
Если ПарамОбмена.ХранилищеТранзакций = Неопределено Тогда
ПарамОбмена.ХранилищеТранзакций = Новый ХранилищеЗначения(ДанныеОтвета.transaction_details, Новый СжатиеДанных(9));
Иначе
ПредМассивТранзакций = ПарамОбмена.ХранилищеТранзакций.Получить();
Для каждого лТранзакция Из ДанныеОтвета.transaction_details Цикл
ПредМассивТранзакций.Добавить(лТранзакция);
КонецЦикла;
ПарамОбмена.ХранилищеТранзакций = Новый ХранилищеЗначения(ПредМассивТранзакций, Новый СжатиеДанных(9));
КонецЕсли;
КонецЕсли;
Если HTTPОтвет.КодСостояния = 401 И номерпопытки = 0 Тогда
номерпопытки = номерпопытки + 1;
ТекстОшибки = "HTTPОтвет.КодСостояния|" + HTTPОтвет.КодСостояния + "|Запрос в API PayPal не выполнен, 401 Not Authorized - получить новый токен?";
ЗаписьЖурналаРегистрации("PayPal", УровеньЖурналаРегистрации.Предупреждение,,,ТекстОшибки);
ПолучитьТокенДоступа();
ВызовPayPal(номерпопытки,,ПарамОбмена);
ИначеЕсли HTTPОтвет.КодСостояния > 299 Тогда
ТекстОшибки = "HTTPОтвет.КодСостояния|" + HTTPОтвет.КодСостояния + "|Запрос в API PayPal не выполнен";
ЗаписьЖурналаРегистрации("PayPal", УровеньЖурналаРегистрации.Предупреждение,,,ТекстОшибки);
КонецЕсли;
ЧтениеJSON.Закрыть();
Исключение
ПарамОбмена.total_pages = 0; // чтобы не было зацикливания
ТекстОшибки = "ОтправкаPayPal|"+ОписаниеОшибки();
ЗаписьЖурналаРегистрации("PayPal",УровеньЖурналаРегистрации.Ошибка,,,ТекстОшибки);
КонецПопытки;
КонецПроцедуры
Чтобы получить именно список транзакций, где содержится нужная мне информация предварительно следует отдельно её включить и подождать 3 часа. Я не проверял точность и продолжил на следующий день.
Приведенные примеры должны работать на любой конфигурации с УФ с версией платформы 8.3. Тестировал в КА 2.4 на платформе 8.3.13.
Другие мои публикации на Инфостарте: