From da40f5f9c0d06dddda4535c0e89b76b816230252 Mon Sep 17 00:00:00 2001 From: Egor Gruzdev Date: Wed, 23 Sep 2026 00:06:29 +0200 Subject: [PATCH 1/2] =?UTF-8?q?GetSettings:=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA=20=D0=BE=D0=B1=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=81=20=D0=B1=D0=B0=D0=BD=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Client::getSettings($inn, $bic, $account) отправляет GetSettings в рамках сессии с заголовками Inn, Bic, Account и AvailableAPIVersion и возвращает разобранные Settings - GetSettingsResponseType в SuccessResultType - в тестах пример ответа из описания транспортного протокола 1С Closes #18 --- src/Client.php | 30 ++++++++ src/ClientInterface.php | 3 + src/Objects/GetSettingsDataType.php | 31 ++++++++ src/Objects/GetSettingsResponseType.php | 77 ++++++++++++++++++++ src/Objects/SuccessResultType.php | 7 ++ tests/ClientHttpTest.php | 75 +++++++++++++++++++ tests/Fixture/xml/1c/GetSettingsResponse.xml | 26 +++++++ tests/Fixture/xml/1c/readme.md | 1 + 8 files changed, 250 insertions(+) create mode 100644 src/Objects/GetSettingsDataType.php create mode 100644 src/Objects/GetSettingsResponseType.php create mode 100644 tests/Fixture/xml/1c/GetSettingsResponse.xml diff --git a/src/Client.php b/src/Client.php index 8a7b3b1..dec61cc 100644 --- a/src/Client.php +++ b/src/Client.php @@ -21,6 +21,7 @@ use TTBooking\DirectBank\Objects\GetPacketListResponseType; use TTBooking\DirectBank\Objects\Packet; use TTBooking\DirectBank\Objects\ResultBank; +use TTBooking\DirectBank\Objects\Settings; class Client implements ClientInterface { @@ -125,6 +126,35 @@ protected function requestPackList(?\DateTimeInterface $since): ?GetPacketListRe return $result->getSuccess()->getGetPacketListResponse(); } + /** + * Настройки обмена с банком в автоматическом режиме (GetSettings). + * Если идентификатор клиента в банке ещё неизвестен, в настройке customerId передаётся '0'. + * + * @param string $inn ИНН организации, для которой запрашиваются настройки + * @param string $bic БИК банка + * @param string|null $account номер расчётного счёта; для зарплатных проектов может отсутствовать + */ + public function getSettings(string $inn, string $bic, ?string $account = null): Settings + { + $headers = array_filter([ + 'inn' => $inn, + 'bic' => $bic, + 'account' => $account, + 'availableapiversion' => $this->settings['availableApiVersion'], + ], fn($value) => $value !== null); + + $result = $this->invoke('POST', 'GetSettings', headers: $headers); + + $response = $result->getSuccess()->getGetSettingsResponse() + ?? throw new UnexpectedResponseException('Bank response to GetSettings has no GetSettingsResponse.'); + + try { + return $response->getSettings(); + } catch (\Throwable $e) { + throw new UnexpectedResponseException('Unable to parse settings from GetSettings response: ' . $e->getMessage(), 0, $e); + } + } + public function getPack(string $uid): Packet { $result = $this->invoke('GET', 'GetPack', query: ['id' => $uid]); diff --git a/src/ClientInterface.php b/src/ClientInterface.php index e84cc91..6c90497 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -5,6 +5,7 @@ use TTBooking\DirectBank\Objects\GetPacketListResponseType; use TTBooking\DirectBank\Objects\Packet; +use TTBooking\DirectBank\Objects\Settings; interface ClientInterface { @@ -19,4 +20,6 @@ public function getPackList(?\DateTimeInterface $dateTime = null): ?array; public function getPackListResponse(\DateTimeInterface|string|null $since = null): GetPacketListResponseType; public function getPack(string $uid) : Packet; + + public function getSettings(string $inn, string $bic, ?string $account = null): Settings; } diff --git a/src/Objects/GetSettingsDataType.php b/src/Objects/GetSettingsDataType.php new file mode 100644 index 0000000..ed48b0c --- /dev/null +++ b/src/Objects/GetSettingsDataType.php @@ -0,0 +1,31 @@ +value; + } + + public function getDockind(): string + { + return $this->dockind; + } +} diff --git a/src/Objects/GetSettingsResponseType.php b/src/Objects/GetSettingsResponseType.php new file mode 100644 index 0000000..5d9cde0 --- /dev/null +++ b/src/Objects/GetSettingsResponseType.php @@ -0,0 +1,77 @@ +id; + } + + public function getFormatVersion(): string + { + return $this->formatVersion; + } + + public function getCreationDate(): string + { + return $this->creationDate; + } + + public function getUserAgent(): ?string + { + return $this->userAgent; + } + + /** + * Настройки обмена в base64 + */ + public function getData(): string + { + return $this->Data->getValue(); + } + + public function getDockind(): string + { + return $this->Data->getDockind(); + } + + /** + * Разобранные настройки обмена + */ + public function getSettings(): Settings + { + $settings = new Settings(); + $settings->mapFromXml(base64_decode($this->getData(), true) ?: throw new \UnexpectedValueException('Settings data is not valid base64.')); + + return $settings; + } +} diff --git a/src/Objects/SuccessResultType.php b/src/Objects/SuccessResultType.php index 3a32621..9738c3d 100644 --- a/src/Objects/SuccessResultType.php +++ b/src/Objects/SuccessResultType.php @@ -14,6 +14,8 @@ class SuccessResultType protected ?Packet $GetPacketResponse = null; + protected ?GetSettingsResponseType $GetSettingsResponse = null; + /** * @return \TTBooking\DirectBank\Objects\LogonResponseType|null */ @@ -36,4 +38,9 @@ public function getGetPacketResponse(): ?Packet { return $this->GetPacketResponse; } + + public function getGetSettingsResponse(): ?GetSettingsResponseType + { + return $this->GetSettingsResponse; + } } \ No newline at end of file diff --git a/tests/ClientHttpTest.php b/tests/ClientHttpTest.php index 844ff7a..cd019dd 100644 --- a/tests/ClientHttpTest.php +++ b/tests/ClientHttpTest.php @@ -7,6 +7,7 @@ use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; +use TTBooking\DirectBank\Dictionary\ErrorCode; use TTBooking\DirectBank\Exceptions\ClientException; use TTBooking\DirectBank\Exceptions\OtpRequiredException; use TTBooking\DirectBank\Exceptions\UnexpectedResponseException; @@ -455,6 +456,80 @@ public function testCreateSessionStoresSessionId() $this->assertSame('SID-1', $this->requests[1]->getHeaderLine('SID')); } + public function testGetSettings() + { + $this->mock->append( + self::logon(), + new Response(200, [], file_get_contents(__DIR__ . '/Fixture/xml/1c/GetSettingsResponse.xml')), + ); + + $settings = $this->createClient(['customerId' => '0'])->getSettings('761700021132', '044525888', '40802810200000099888'); + + $this->assertSame('EFD857B5-7FA8-4195-8666-2CCADBC3C8DE', $settings->getId()); + $this->assertSame('2806', $settings->getData()->getCustomerID()); + $this->assertSame('https://dbogate.demobank.ru/', $settings->getData()->getBankServerAddress()); + $this->assertSame('user_login', $settings->getData()->getLogon()->getLogin()->getUser()); + $this->assertSame(['03', '05', '10', '11', '14', '30'], $settings->getData()->getDocKinds()); + + [$logon, $request] = $this->requests; + + $this->assertStringEndsWith('/Logon', $logon->getUri()->getPath()); + $this->assertSame('POST', $request->getMethod()); + $this->assertStringEndsWith('/GetSettings', $request->getUri()->getPath()); + $this->assertSame('0', $request->getHeaderLine('CustomerID')); + $this->assertSame('SID-1', $request->getHeaderLine('SID')); + $this->assertSame('761700021132', $request->getHeaderLine('Inn')); + $this->assertSame('044525888', $request->getHeaderLine('Bic')); + $this->assertSame('40802810200000099888', $request->getHeaderLine('Account')); + $this->assertSame('2.2.2', $request->getHeaderLine('APIVersion')); + $this->assertSame('2.2.2', $request->getHeaderLine('AvailableAPIVersion')); + $this->assertFalse($request->hasHeader('Authorization')); + $this->assertSame('', (string) $request->getBody()); + } + + public function testGetSettingsWithoutAccount() + { + $this->mock->append(new Response(200, [], file_get_contents(__DIR__ . '/Fixture/xml/1c/GetSettingsResponse.xml'))); + + $this->createClient(['sessionId' => 'SID-0', 'availableApiVersion' => null])->getSettings('7705260699', '044525888'); + + $this->assertFalse($this->requests[0]->hasHeader('Account')); + $this->assertFalse($this->requests[0]->hasHeader('AvailableAPIVersion')); + } + + public function testGetSettingsError() + { + $this->mock->append(self::error('1012', 'Не удалось получить настройки обмена с банком')); + + try { + $this->createClient(['sessionId' => 'SID-0'])->getSettings('7705260699', '044525888'); + $this->fail('ClientException expected'); + } catch (ClientException $e) { + $this->assertSame(ErrorCode::SETTINGS_UNAVAILABLE, $e->getBankCode()); + } + } + + public function invalidSettingsResponseProvider(): array + { + return [ + 'no GetSettingsResponse' => [self::success('PACK-1')], + 'not base64' => [self::success('!!!')], + 'not settings' => [self::success('' . base64_encode('not xml') . '')], + ]; + } + + /** + * @dataProvider invalidSettingsResponseProvider + */ + public function testGetSettingsUnexpectedResponse(Response $response) + { + $this->mock->append($response); + + $this->expectException(UnexpectedResponseException::class); + + $this->createClient(['sessionId' => 'SID-0'])->getSettings('7705260699', '044525888'); + } + public function testBankErrorWithHttpErrorStatus() { $this->mock->append(self::error('1009', 'Ошибка приемного сервиса', 500)); diff --git a/tests/Fixture/xml/1c/GetSettingsResponse.xml b/tests/Fixture/xml/1c/GetSettingsResponse.xml new file mode 100644 index 0000000..213a1ea --- /dev/null +++ b/tests/Fixture/xml/1c/GetSettingsResponse.xml @@ -0,0 +1,26 @@ + + + + + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPFNldHRpbmdzIHhtbG5zPSJo +dHRwOi8vZGlyZWN0YmFuay4xYy5ydS9YTUxTY2hlbWEiCiAgICB4bWxuczp4c2k9Imh0dHA6Ly93 +d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIgogICAgeG1sbnM6eHNkPSJodHRwOi8v +d3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIKICAgIGlkPSJFRkQ4NTdCNS03RkE4LTQxOTUtODY2 +Ni0yQ0NBREJDM0M4REUiCiAgICBmb3JtYXRWZXJzaW9uPSIyLjIuMSIKICAgIGNyZWF0aW9uRGF0 +ZT0iMjAxNi0wNC0yMlQwOTozODo1MSIgCiAgICB1c2VyQWdlbnQ9IkRlbW9CYW5rU2VydmljZSI+ +Cgk8U2VuZGVyIGJpYz0iMDQ0NTI1ODg4IiBuYW1lPSLQlNCV0JzQni3QkdCQ0J3QmiIgLz4KICAg +IDxSZWNpcGllbnQgaWQ9IjI4MDYiIG5hbWU9ItCi0L7RgNCz0L7QstGL0Lkg0LTQvtC8INCa0L7Q +vNC/0LvQtdC60YHQvdGL0LkiIGlubj0iNzcwNTI2MDY5OSIga3BwPSI3NzA1MDEwMDEiIC8+CiAg +ICA8RGF0YT4KICAgICAgICA8Q3VzdG9tZXJJRD4yODA2PC9DdXN0b21lcklEPgogICAgICAgIDxC +YW5rU2VydmVyQWRkcmVzcz5odHRwczovL2Rib2dhdGUuZGVtb2JhbmsucnUvPC9CYW5rU2VydmVy +QWRkcmVzcz4KICAgICAgICA8Rm9ybWF0VmVyc2lvbj4yLjIuMTwvRm9ybWF0VmVyc2lvbj4KICAg +ICAgICA8RW5jb2Rpbmc+VVRGLTg8L0VuY29kaW5nPgogICAgICAgIDxMb2dvbj4KICAgICAgICAg +ICAgPExvZ2luPgogICAgICAgICAgICAgICAgPFVzZXI+dXNlcl9sb2dpbjwvVXNlcj4KICAgICAg +ICAgICAgPC9Mb2dpbj4KICAgICAgICA8L0xvZ29uPgogICAgICAgIDxEb2N1bWVudCBkb2NLaW5k +PSIwMyIgLz4KICAgICAgICA8RG9jdW1lbnQgZG9jS2luZD0iMDUiIC8+CiAgICAgICAgPERvY3Vt +ZW50IGRvY0tpbmQ9IjEwIiAvPgogICAgICAgIDxEb2N1bWVudCBkb2NLaW5kPSIxMSIgLz4JCQog +ICAgICAgIDxEb2N1bWVudCBkb2NLaW5kPSIxNCIgLz4KCQk8RG9jdW1lbnQgZG9jS2luZD0iMzAi +IC8+CiAgICA8L0RhdGE+CjwvU2V0dGluZ3M+ + + + diff --git a/tests/Fixture/xml/1c/readme.md b/tests/Fixture/xml/1c/readme.md index cbd777b..1bea9d2 100644 --- a/tests/Fixture/xml/1c/readme.md +++ b/tests/Fixture/xml/1c/readme.md @@ -6,5 +6,6 @@ - Электронные документы — : `CancelationRequest.xml`, `PayDocRu.xml`, `PayRequest.xml`, `Probe.xml`, `Settings.xml`, `Settings_Logon_Certificate.xml`, `Statement.xml`, `StatementRequest.xml`, `StatusDocNotice.xml`, `StatusRequest.xml` +- `GetSettingsResponse.xml` — пример ответа на GetSettings из - `Packet.xml` — пример запроса SendPack из . Подпись в примере обрезана и не является корректным base64, поэтому XSD файл не проходит. From 04c1a699466f17beab9f90da93ab0f167fdf2f3d Mon Sep 17 00:00:00 2001 From: Egor Gruzdev Date: Wed, 23 Sep 2026 00:06:29 +0200 Subject: [PATCH 2/2] =?UTF-8?q?README:=20=D0=BD=D0=B0=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B9=D0=BA=D0=B8=20=D0=BE=D0=B1=D0=BC=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20GetSettings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index cdb8eac..291ba8a 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ try { | `getPackList(?DateTimeInterface $date = null): ?array` | `GET GetPackList` | список идентификаторов контейнеров, готовых к получению | | `getPackListResponse(DateTimeInterface\|string\|null $since = null)` | `GET GetPackList` | список контейнеров и отметка времени последнего из них | | `getPack(string $id): Packet` | `GET GetPack` | транспортный контейнер | +| `getSettings(string $inn, string $bic, ?string $account = null): Settings` | `POST GetSettings` | настройки обмена с банком | Отметка времени для списка контейнеров задаётся по часам сервера банка и передаётся в формате `dd.MM.yyyy HH:mm:ss`. Чтобы получать только новые контейнеры, передавайте @@ -116,6 +117,22 @@ foreach ($list->getPacketID() as $id) { $lastTimestamp = $list->getTimeStampLastPacket() ?? $lastTimestamp; // сохранить до следующего запроса ``` +### Настройки обмена + +Настройки обмена с банком можно получить автоматически. Если идентификатор клиента в банке ещё +неизвестен, в `customerId` передаётся `'0'`; из настроек его можно взять для дальнейшей работы: + +```php +$settings = (new Client(['customerId' => '0'] + $credentials)) + ->getSettings(inn: '7705260699', bic: '044525888', account: '40702810813123123222'); + +$data = $settings->getData(); +$data->getCustomerID(); // идентификатор клиента в банке +$data->getBankServerAddress(); // адрес ресурса банка +$data->getDocKinds(); // виды документов, которыми возможен обмен: ['03', '05', '10', ...] +$data->getLogon()->getLogin()?->getUser(); +``` + ### Ошибки - `TTBooking\DirectBank\Exceptions\ClientException` — банк вернул ошибку (`ResultBank/Error`),