Запрос на получения токена подключения Сбер(Авторизация)

1. 01.11.21 08:49 Сейчас в теме
Добрый день! Есть задача интеграции Плати QR от Сбербанка. Весь процесс запросов заказов на оплату начинается с запроса на получения токена соединения. Я зарегистрировался и получил ID и пароль, а также сертификат OpenSSL для теста. В тестовом шаблоне проверки API в личном кабинете запрос отрабатывает. А при попытке в тестовой обработке. Ответ 404. После ответа от поддержки получил пример авторизации на Pyton. Она выглядит так:
# -*- coding: utf8 -*-
import http.client, ssl, json, base64
from random import choice as c
client_id = ''
client_secret = ''
base64 = base64.b64encode(f'{client_id}:{client_secret}'.encode('utf-8')).decode('utf-8')
def rquid() -> str:
"""Random choice string"""
return ''.join([c('ABCDEFabcdef1234567890') for _ in range(32)])
def token(scope: str) -> None:
"""Requests Token OAUTH"""
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('./client_cert.crt', './private_key.key')
conn = http.client.HTTPSConnection('api.sberbank.ru', port=443, context=context)
payload = f'grant_type=client_credentials&scope={scope}'
headers = {'accept': 'application/json',
'content-type': 'application/x-www-form-urlencoded',
'rquid': rquid(),
'authorization': f'Basic {base64}',
'x-ibm-client-id': client_id}
conn.request('POST', 'ru/prod/tokens/v2/oauth', payload, headers)
data = conn.getresponse().read()
print(json.loads(data.decode('utf-8'))['access_token'])
token('scope').

Сертификат безопасного подключения описан в запросе как личный + файл ключа
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('./client_cert.crt', './private_key.key')

В 1С я не смог это реализовать. При помощи OpenSSL я разбил .pfx файл сертификата на три: client_cert.crt, cacerts.cer и private.key.
Добавил описание в bin\cacert.pem описание ключа в формате md5. Вроде все есть. А вот метода подобного Pyton нет.
Только
ssl1 = Новый ЗащищенноеСоединениеOpenSSL(
Новый СертификатКлиентаWindows(СпособВыбораСертификатаWindows.Авто),
Новый СертификатыУдостоверяющихЦентровWindows());

ssl2 = Новый ЗащищенноеСоединениеOpenSSL(
Новый СертификатКлиентаWindows(),
Новый СертификатыУдостоверяющихЦентровФайл("C:/certs/myca.pem"));

ssl3 = Новый ЗащищенноеСоединениеOpenSSL(
Новый СертификатКлиентаФайл("C:/certs/user.p12", "password" ),
Новый СертификатыУдостоверяющихЦентровФайл("C:/certs/myca.pem"));

ssl4 = Новый ЗащищенноеСоединениеOpenSSL( неопределено, неопределено );

ssl5 = Новый ЗащищенноеСоединениеOpenSSL(
Новый СертификатКлиентаФайл("C:/certs/user.p13", "password" ),
Новый СертификатыУдостоверяющихЦентровОС());
Не знаю как решить эту проблему.
Найденные решения
3. ElGatoGris 01.11.21 11:54 Сейчас в теме
404 - не найдена страница. Адрес сервера "api.sberbank.ru" (без https) указывается в параметрах соединения, а путь к ресурсу "ru/prod/tokens/v2/oauth" указывается при конструировании запроса как
HTTPЗапрос = Новый HTTPЗапрос("/ru/prod/tokens/v2/oauth" ); 


Вот мой код для получения кода доступа в сервисе Google

	Соединение = Новый HTTPСоединение(
        "oauth2.googleapis.com", // сервер (хост)
        443, // порт, по умолчанию для http используется 80, для https 443
        , // пользователь для доступа к серверу (если он есть)
        , // пароль для доступа к серверу (если он есть)
        , // здесь указывается прокси, если он есть
        , // таймаут в секундах, 0 или пусто - не устанавливать
		Новый ЗащищенноеСоединениеOpenSSL()
    );
	
	RefreshToken = Ключи["refresh_token"];
	
 	Запрос = Новый HTTPЗапрос("/token");
	
	ТелоЗапроса = "grant_type=refresh_token";
	ТелоЗапроса = ТелоЗапроса + "&refresh_token="+RefreshToken;
	ТелоЗапроса = ТелоЗапроса + "&client_id= ";
	ТелоЗапроса = ТелоЗапроса + "&client_secret= ";

	Запрос.УстановитьТелоИзСтроки(ТелоЗапроса, "UTF-8", ИспользованиеByteOrderMark.НеИспользовать);
	Запрос.Заголовки.Вставить("Content-Type", "Application/X-Www-Form-Urlencoded");
	
	Результат = Соединение.ВызватьHTTPМетод("POST", Запрос);
	
	ОтветСтрока = Результат.ПолучитьТелоКакСтроку();
	
	ЧтениеJSON = Новый ЧтениеJSON;
	ЧтениеJSON.УстановитьСтроку(ОтветСтрока); 
	Ответ = ПрочитатьJSON(ЧтениеJSON);
	
	Ключи["access_token"] = Ответ["access_token"];
Показать
Остальные ответы
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
2. nikiforovvn 01.11.21 09:08 Сейчас в теме
Процедура АвторизацияНаСервере()
	GUID = Новый УникальныйИдентификатор();
	СтрокаGUID = СтрЗаменить(Строка(GUID),"-","");
	ID = "837c77a6-2aab-4480-9271-71b85b28****";
	Secret = "M3kJ5bS3pA8nF1bC7tP0tR6hY8dF6rX4lM1oI4rE8wV4fA****";
	IDSecret64 = ШтрихкодВBase64(ID + ":" + Secret); 
	Authorization = "Basic " + IDSecret64; 
	                            		
	ssl = Новый ЗащищенноеСоединениеOpenSSL
			(Новый СертификатКлиентаФайл("H:\QR\nikiforovvn@mail.ru.p12","PlatiQR2021"));

	HTTPСоединение = Новый HTTPСоединение("dev.api.sberbank.ru",443,,,,,ssl);
			
	ПараметрыЗапроса = "?grant_type=client_credentials&scope=https%3A%2F%2Fapi.sberbank.ru%2Forder.create"; //
	ТекстЗапроса = "https://api.sberbank.ru/ru/prod/tokens/v2/oauth";
		
	HTTPЗапрос = Новый HTTPЗапрос(ТекстЗапроса);
	HTTPЗапрос.АдресРесурса = ТекстЗапроса + ПараметрыЗапроса; 
	HTTPЗапрос.Заголовки.Вставить("x-ibm-client-id", ID);	
	HTTPЗапрос.Заголовки.Вставить("authorization", Authorization);	
	HTTPЗапрос.Заголовки.Вставить("rquid", СтрокаGUID);	
	HTTPЗапрос.Заголовки.Вставить("content-type","application/x-www-form-urlencoded" ); //"json"	
	HTTPЗапрос.Заголовки.Вставить("accept", "application/json");


	Ответ = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);

КонецПроцедуры
Показать
3. ElGatoGris 01.11.21 11:54 Сейчас в теме
404 - не найдена страница. Адрес сервера "api.sberbank.ru" (без https) указывается в параметрах соединения, а путь к ресурсу "ru/prod/tokens/v2/oauth" указывается при конструировании запроса как
HTTPЗапрос = Новый HTTPЗапрос("/ru/prod/tokens/v2/oauth" ); 


Вот мой код для получения кода доступа в сервисе Google

	Соединение = Новый HTTPСоединение(
        "oauth2.googleapis.com", // сервер (хост)
        443, // порт, по умолчанию для http используется 80, для https 443
        , // пользователь для доступа к серверу (если он есть)
        , // пароль для доступа к серверу (если он есть)
        , // здесь указывается прокси, если он есть
        , // таймаут в секундах, 0 или пусто - не устанавливать
		Новый ЗащищенноеСоединениеOpenSSL()
    );
	
	RefreshToken = Ключи["refresh_token"];
	
 	Запрос = Новый HTTPЗапрос("/token");
	
	ТелоЗапроса = "grant_type=refresh_token";
	ТелоЗапроса = ТелоЗапроса + "&refresh_token="+RefreshToken;
	ТелоЗапроса = ТелоЗапроса + "&client_id= ";
	ТелоЗапроса = ТелоЗапроса + "&client_secret= ";

	Запрос.УстановитьТелоИзСтроки(ТелоЗапроса, "UTF-8", ИспользованиеByteOrderMark.НеИспользовать);
	Запрос.Заголовки.Вставить("Content-Type", "Application/X-Www-Form-Urlencoded");
	
	Результат = Соединение.ВызватьHTTPМетод("POST", Запрос);
	
	ОтветСтрока = Результат.ПолучитьТелоКакСтроку();
	
	ЧтениеJSON = Новый ЧтениеJSON;
	ЧтениеJSON.УстановитьСтроку(ОтветСтрока); 
	Ответ = ПрочитатьJSON(ЧтениеJSON);
	
	Ключи["access_token"] = Ответ["access_token"];
Показать
5. nikiforovvn 01.11.21 13:31 Сейчас в теме
(3)
"&refresh_token="+RefreshToken;

Спасибо большое! Вот готовый запрос на токен авторизации. Далее запросы на оплату. Тема не закрыта.
&НаСервере
Процедура АвторизацияНаСервере()
	GUID = Новый УникальныйИдентификатор();
	СтрокаGUID = СтрЗаменить(Строка(GUID),"-","");
	ID = "837c77a6-2aab-4480-9271-71b85b280***";
	Secret = "M3kJ5bS3pA8nF1bC7tP0tR6hY8dF6rX4lM1oI4rE8wV4fA***";
	IDSecret64 = ШтрихкодВBase64(ID + ":" + Secret); 
	Authorization = "Basic " + IDSecret64; 
				
	ssl = Новый ЗащищенноеСоединениеOpenSSL
	(Новый СертификатКлиентаФайл("H:\QR\private.key","PlatiQR2021")); //400
		
	HTTPСоединение = Новый HTTPСоединение("dev.api.sberbank.ru",443,,,,,ssl);
				
	ТелоЗапроса = "grant_type=client_credentials";
    ТелоЗапроса = ТелоЗапроса + "&scope=https%3A%2F%2Fapi.sberbank.ru%2Forder.create";
	
	ТекстЗапроса = "/ru/prod/tokens/v2/oauth";
		
	HTTPЗапрос = Новый HTTPЗапрос(ТекстЗапроса);
	HTTPЗапрос.АдресРесурса = ТекстЗапроса;//  + ТелоЗапроса
	HTTPЗапрос.Заголовки.Вставить("x-ibm-client-id", ID);	
	HTTPЗапрос.Заголовки.Вставить("authorization", Authorization);	
	HTTPЗапрос.Заголовки.Вставить("rquid", СтрокаGUID);	
	HTTPЗапрос.Заголовки.Вставить("content-type","application/x-www-form-urlencoded" ); //"json"	
	HTTPЗапрос.Заголовки.Вставить("accept", "application/json");
	
	HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапроса,КодировкаТекста.UTF8,ИспользованиеByteOrderMark.НеИспользовать); //

	Результат = HTTPСоединение.ВызватьHTTPМетод("POST",HTTPЗапрос);

	ОтветСтрока = Результат.ПолучитьТелоКакСтроку();
    
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.УстановитьСтроку(ОтветСтрока); 
    Ответ = ПрочитатьJSON(ЧтениеJSON);
КонецПроцедуры
Показать
14. user598199_serega240710 25 22.12.21 14:27 Сейчас в теме
(5)Выполнил по аналогии, подставив свои значения ID и Secret. Выдает ошибку: {"httpCode":"400", "httpMessage":"Bad Request", "moreInformation":"invalid_scope"}

UID 			= Новый УникальныйИдентификатор();
    СтрокаUID 		= СтрЗаменить(Строка(UID), "-", "");
    ID 				= "171170c2-ed76-4b44-9263-xxx";
    Secret 			= "ea71fac3-5c4f-435b-8314-xxx";
    IDSecret64 		= Base64Строка(ПолучитьДвоичныеДанныеИзСтроки(ID + ":" + Secret));  
    Authorization 	= "Basic " + IDSecret64;  
                
    ssl = Новый ЗащищенноеСоединениеOpenSSL(Новый СертификатКлиентаФайл("Y:\private.key", "xxx")); //400
        
    HTTPСоединение = Новый HTTPСоединение("api.sberbank.ru", 8443,,,,, ssl);
                
    ТелоЗапроса = "grant_type=client_credentials";
	ТелоЗапроса = ТелоЗапроса + "&scope=https%3A%2F%2Fapi.sberbank.ru%2Forder.create";
    
    ТекстЗапроса = "/ru/prod/tokens/v2/oauth";
        
    HTTPЗапрос = Новый HTTPЗапрос(ТекстЗапроса);
	HTTPЗапрос.АдресРесурса = ТекстЗапроса;
	HTTPЗапрос.Заголовки.Вставить("Authorization",		Authorization);
	HTTPЗапрос.Заголовки.Вставить("Accept", 			"application/json");
	HTTPЗапрос.Заголовки.Вставить("X-IBM-Client-Id", 	ID);    
	HTTPЗапрос.Заголовки.Вставить("RqUID", 				СтрокаUID);    
	HTTPЗапрос.Заголовки.Вставить("Content-Type",		"application/x-www-form-urlencoded" );    
	
    HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапроса, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать); 

    Результат = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);

    ОтветСтрока = Результат.ПолучитьТелоКакСтроку();
    
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.УстановитьСтроку(ОтветСтрока); 
    Ответ = ПрочитатьJSON(ЧтениеJSON);
Показать
15. user598199_serega240710 25 23.12.21 16:22 Сейчас в теме
(14)Нужно использовать новое значение scope "https%3A%2F%2Fapi.sberbank.ru%2Fqr%2Forder.create"
4. nikiforovvn 01.11.21 12:53 Сейчас в теме
Вот благодаря вам уже Ошибка 400 и результат
 GUID = Новый УникальныйИдентификатор();
	СтрокаGUID = СтрЗаменить(Строка(GUID),"-","");
	ID = "837c77a6-2aab-4480-9271-71b85b280***";
	Secret = "M3kJ5bS3pA8nF1bC7tP0tR6hY8dF6rX4lM1oI4rE8wV4fA4***";
	IDSecret64 = ШтрихкодВBase64(ID + ":" + Secret); 
	Authorization = "Basic " + IDSecret64; 
			
	ssl = Новый ЗащищенноеСоединениеOpenSSL
	(Новый СертификатКлиентаФайл("H:\QR\private.key","PlatiQR2021"));

	HTTPСоединение = Новый HTTPСоединение("dev.api.sberbank.ru",443,,,,,ssl);
			
	ТелоЗапроса = "?grant_type=client_credentials&scope=https%3A%2F%2Fapi.sberbank.ru%2Forder.create"; //
	ТекстЗапроса = "/ru/prod/tokens/v2/oauth";
		
	HTTPЗапрос = Новый HTTPЗапрос(ТекстЗапроса);
	HTTPЗапрос.АдресРесурса = ТекстЗапроса + ТелоЗапроса; 
	HTTPЗапрос.Заголовки.Вставить("x-ibm-client-id", ID);	
	HTTPЗапрос.Заголовки.Вставить("authorization", Authorization);	
	HTTPЗапрос.Заголовки.Вставить("rquid", СтрокаGUID);	
	HTTPЗапрос.Заголовки.Вставить("content-type","application/x-www-form-urlencoded" ); 	
	HTTPЗапрос.Заголовки.Вставить("accept", "application/json");	
	HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапроса,КодировкаТекста.UTF8 , ИспользованиеByteOrderMark.НеИспользовать); //
	Ответ = HTTPСоединение.ВызватьHTTPМетод("POST",HTTPЗапрос);

"content-type"                           	  "application/json"
"X-RateLimit-Limit"	                          "name=token-test-rate-limit,100;"
"Access-Control-Allow-Methods"	  "POST"
"Access-Control-Expose-Headers"	  "APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID"
"X-Global-Transaction-ID"	         "36eaba96617fb77906c04785"
"X-RateLimit-Remaining"	          "name=token-test-rate-limit,99;"
"User-Agent"	                                  "IBM-APIConnect/5.0"
"Access-Control-Allow-Origin"       	"*"
"Date"	                                         "Mon, 01 Nov 2021 09:46:33 GMT"
"Connection"	                                 "Keep-Alive"
"X-Backside-Transport"           	"OK OK"
"Transfer-Encoding"                	"chunked"
Показать
6. ElGatoGris 01.11.21 15:21 Сейчас в теме
400 - значит неверный формат запроса или неверное имя ключа или неверный формат данных ключа или ещё что-то )).

Я для отладки использовал сниффер (WireShark). У меня так же был работающий код на Питоне и я сравнивал запросы. Перед этим надо переключиться с https на http. Сервер, конечно, прогонит, но ответ в данном случае не нужен.
7. nikiforovvn 01.11.21 15:36 Сейчас в теме
(6)
В 5 ответе, уже работающий вариант. За сниффер (WireShark), отдельное спасибо.
8. nikiforovvn 01.11.21 15:39 Сейчас в теме
(6)
Ругалось вот так:

{"httpCode":"400","httpMessage":"Bad Request","moreInformation":"local:///authgw/auth/1.0/xsd/Token2Rq.jsd:42: [JSV0001] Invalid value type 'string'."}
В теле запроса параметры grant_type или scope содержат спецсимволы #*!$@%^&*()_+=-'".
Корректные данные параметров для вызова токена можно посмотреть в Параметрах запроса.

Потом я разложил тело запроса как у вас.
ТелоЗапроса = "grant_type=client_credentials";
ТелоЗапроса = ТелоЗапроса + "&scope=https%3A%2F%2Fapi.sberbank.ru%2Forder.create";

ТекстЗапроса = "/ru/prod/tokens/v2/oauth";

HTTPЗапрос = Новый HTTPЗапрос(ТекстЗапроса);

и убрал параметры из ТекстЗапроса
HTTPЗапрос.АдресРесурса = ТекстЗапроса;// + ТелоЗапроса,
Показать

но и без уточнения HTTPЗапрос.АдресРесурса не работало.
9. cassper 02.11.21 17:03 Сейчас в теме
Тоже сейчас изучаю этот вопрос. После общения с техподдержкой сбербанка для тестирования в песочнице авторизации и создания заказов все работает без использования сертификата.

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

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

ТС, а вы определились, какие значения нужно использовать при создании заказа: member_ID и Id_QR? Тестовые, что в описании API (https://developer.sberbank.ru/api/5df4ada7e4b05210f32c030d) или ТП вам значения выдали?
10. nikiforovvn 05.11.21 11:35 Сейчас в теме
(9)
Сейчас все в стадии регистрации организации. member_ID - выдали после заполнения анкеты и Id_QR должен менеджер сбера сообщить (ID виртуального терминала). После регистрации организации в новом ЛК v3 разработчика будут доступны подписки, пока все в ожидании до 10 октября. У нас сценарий QR Покупателя, поэтому тест возможен только на боевом контуре. Файл сертификата я разбивал на три через openSSL. Он есть в "C:\Apache24\bin\openssl.exe".

openssl pkcs12 -in "H:\QR\nikiforovvn@mail.ru.p12" -passin pass:PlatiQR2021 -nodes -nocerts -out H:\QR\private.key -passout pass:PlatiQR2021
openssl pkcs12 -in "H:\QR\nikiforovvn@mail.ru.p12" -passin pass:PlatiQR2021 -clcerts -nokeys -out H:\QR\client_cert.crt
openssl pkcs12 -in "H:\QR\nikiforovvn@mail.ru.p12" -passin pass:PlatiQR2021 -cacerts -nokeys -chain -out H:\QR\cacerts.cer

PlatiQR2021 - пароль при формировании сертификата.

Потом из client_cert.crt в md5 форматировал. (client_cert.pem) Здесь описание команд openSSL - https://www.openssl.org/docs/man1.0.2/man1/pkcs12.html
Текст из client_cert.pem вставил в конец файла "C:\Program Files (x86)\1cv8\8.3.18.1208\bin\cacert.pem"
11. cassper 08.11.21 11:37 Сейчас в теме
(10) Спасибо за ответ.

Сертификат тоже смог разбить, но разве недостаточно при создании ssl подключения в 1с указать файл p12 и пароль, чтобы не добавлять в файл cacert.pem информацию по сертификату? Или не взлетело подключение? Дело в том, что на тестовом контуре сертификат не нужен, поэтому явно проверить не удалось.

Я планирую использовать QR-код продавца под каждый заказ покупателя.

В принципе были протестированы все возможные функции API (сделаны заготовки для реальной базы), осталось как и вам дождаться регистрации организации.

Регистрацию уже делали на сайте https://api.developer.sber.ru/ ?
Потому же все тестовые запросы я формировал через сайт https://developer.sberbank.ru
17. Aleksnow 13.01.22 19:30 Сейчас в теме
(10) я что-то не понял, в итоге у вас на рабочем сервере взлетело все ? Я после обновления их серверов так и не смог восстановить работу своей интеграции.
12. nikiforovvn 09.11.21 08:52 Сейчас в теме
(11)
По поводу файла - cacert.pem. У меня сразу не взлетело и я искал все возможные затыки. А так пришлось на двух площадках регистрироваться тоже. Мы пока решили QR Покупателя, хотел оба сразу, но сказали нет пока...
13. olbu 22.12.21 13:03 Сейчас в теме
(10) Добрый день!
Похоже Сбер переделал что-то,
Потому же все тестовые запросы я формировал через сайт https://developer.sberbank.ru

Захожу на этот сайт, там нет примеров кодов, либо я не увидел...
16. Aleksnow 13.01.22 15:08 Сейчас в теме
Я так и не понял) Какой по итогу результат? Сам в прошлом году делал интеграцию для загрузки счетов эскроу. Но в декабре у сбера обновились сервера, и все перестало работать) Теперь все время приходит ответ 400
<ht ml>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.20.1</center>
</body>
</html>

Причем делая аналогичный запрос в postman, все работает корректно) Где-то уже месяц с поддержкой общаюсь, и результатов 0.
Запросов моих не видно, логов нет. Хотя ответ то приходит от их сервера.

Вот такие пироги, и куда копать уже не знаю.
18. user598199_serega240710 25 14.01.22 09:42 Сейчас в теме
(16)Попробуйте использовать использовать новое значение scope "https%3A%2F%2Fapi.sberbank.ru%2Fqr%2Forder.create"
Тоже была ошибка 400, оказалось, что они добавили вставку qr в тело запроса.

ТелоЗапроса = "grant_type=client_credentials";
ТелоЗапроса = ТелоЗапроса + "&scope=https%3A%2F%2Fapi.sberbank.ru%2Fqr%2Forder.create";
19. Aleksnow 14.01.22 14:31 Сейчас в теме
(18) Такое тоже пробовал. Тут где то проблема с передачей сертификата. Уже преобразовал p12 в pfx, так как меня получается сразу разворачивает nginx. А тех. поддержка уже тупо не отвечает) В последний раз был разговор что через postman работает же у вас, я говорю да, ну так разбирайтесь с вашем 1С, мы то чего =)
Оставьте свое сообщение
Вопросы с вознаграждением
Вакансии
Аналитик 1С
Санкт-Петербург
зарплата до 150 000 руб.
Полный день

Руководитель отдела разработки
Москва
зарплата от 200 000 руб. до 230 000 руб.
Полный день

Программист 1С
Москва
зарплата от 150 000 руб. до 150 000 руб.
Полный день

Консультант-аналитик 1С
Москва
зарплата от 100 000 руб. до 120 000 руб.
Полный день

Программист, аналитик, эксперт 1С
Санкт-Петербург
По совместительству