9 Commits

Author SHA1 Message Date
9b62a59705 saloon update 2026-04-25 13:59:25 +03:00
Anatoliy Poloz
d4db68f2e7 refactor(error): handling 2026-04-07 18:47:53 +03:00
Anatoliy Poloz
ea374d2696 doc(test-payin) added 2026-04-07 18:44:27 +03:00
Anatoliy Poloz
13c91ad108 bump versions 2026-04-05 09:37:54 +03:00
Anatoliy Poloz
3cf15b7913 refactor(qr) 2026-04-02 16:45:41 +03:00
Anatoliy Poloz
a05a6506e8 fix(sign) 2026-03-21 07:10:16 +03:00
Anatoliy Poloz
4f52a189c7 add Identification 2026-03-19 19:38:21 +03:00
Anatoliy Poloz
b7b2a8fd61 refactor(Sbp) 2026-03-17 12:26:30 +03:00
Anatoliy Poloz
e2adc8d530 fix(OpenSslCmsSigner) 2026-03-10 22:29:35 +03:00
35 changed files with 1065 additions and 262 deletions

View File

@@ -0,0 +1,137 @@
какой email технической поддержки ?
10:36
Доставлено
Для связи с технической поддержкой используйте электронную почту alfa\_api@alfabank.ru.
10:36
как происходит flow регистрации бенифициара ?
10:53
Доставлено
Для регистрации бенефициара необходимо выполнить следующие шаги:
1. 1.
**Создать запрос на регистрацию бенефициара** с помощью метода:
- •
**Песочница**: `POST https://sandbox.alfabank.ru/api/na/jp/v1/beneficiaries`
- •
**Пром**: `POST https://baas.alfabank.ru/api/na/jp/v1/beneficiaries`
При этом нужно учитывать, что в зависимости от типа бенефициара поля запроса заполняются по-разному.
2. 2.
3. 3.
При успешной регистрации бенефициару присваивается уникальный идентификатор — `beneficiaryId`.
**Важно:** учёт денежных средств на виртуальных счетах бенефициаров будет вестись корректно только при условии, что владелец номинального счёта не использует другие каналы вывода средств с номинального счёта (например, банк-клиент, платёжные поручения на бумажном носителе, сервисы Alfa API для осуществления переводов и другие).
Ссылки на документацию:
- •
- •
- •
10:53
а как же socring ?
10:55
Доставлено
Для проверки актуальности ранее зарегистрированных данных о бенефициаре используются следующие методы:
1. 1.
**«Создание запроса на проверку бенефициара»** — инициирует процесс скоринга бенефициара.
2. 2.
**«Получение статуса запроса»** — позволяет отслеживать статус ранее созданного запроса на проверку.
**Ссылки на документацию:**
- •
- •
10:55
каким образом можно пополнить номинальный счёт компании ?
22:24
Доставлено
Депонент (бенефициар) может пополнить номинальный счёт удобным ему способом. После этого владелец номинального счёта должен идентифицировать поступившие денежные средства. Для этого владельцу счёта необходимо выполнить следующие шаги:
1. 1.
2. 2.
3. 3.
22:25
каким образом можно пополнить номинальный счёт компании ?
22:25
Доставлено
Депонент (бенефициар) может пополнить номинальный счёт удобным ему способом. После этого владелец номинального счёта должен идентифицировать поступившие денежные средства. Для этого владельцу счёта необходимо выполнить следующие шаги:
1. 1.
2. 2.
3. 3.
**Важно:** для работы с сервисом необходимо использовать электронную подпись. Допускается использование сертификата, выпущенного с помощью [сервиса «Электронная подпись»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/signature/articles/intro/intro), или сертификата УКЭП, выпущенного в любом аккредитованном УЦ.
Ссылка на статью с подробным описанием процесса: [Идентификация пополнений номинального счёта](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/intro/intro).
22:25
а что это значит "любым удобным способом"? каким например ?
22:25
Доставлено
В предоставленной документации нет конкретного перечня способов пополнения номинального счёта. Однако указано, что депонент (бенефициар) может пополнить номинальный счёт удобным ему способом. После этого владелец номинального счёта должен идентифицировать поступившие денежные средства с помощью специальных методов API.
Для идентификации пополнений владелец счёта может использовать следующие методы:
1. 1.
2. 2.
3. 3.
Для более детальной информации о возможных способах пополнения номинального счёта рекомендуется обратиться на почту alfa\_api@alfabank.ru.
22:25

View File

@@ -0,0 +1,153 @@
---
title: "Интеграция с Альфа-Банком: API, документация, примеры кода"
source: "https://developers.alfabank.ru/products/alfa-api/documentation/articles/sandbox/articles/nominal-accounts/articles/intro/intro"
author:
published:
created: 2026-04-07
description: "Описания API-методов, примеры запросов, инструкции по настройке интеграции для работы с продуктами и сервисами Альфа-Банка"
tags:
- "clippings"
---
#### Инструкция по тестированию
Рекомендации по тестированию [номинальных счетов](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/intro/intro).
##### Идентификация пополнений
Для тестирования методов для идентификации пополнений номинального счёта предварительно необходимо создать тестовое неидентифицированное пополнение с помощью метода [«Создание тестового неидентифицированного пополнения номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/sandbox/articles/nominal-accounts/articles/unidentified-transaction/v1/unidentified-transaction).
В результате успешного запроса будет создано тестовое неидентифицированное пополнение c идентификатором `id`, который далее необходимо будет передать в методе [«Создание запроса на идентификацию пополнения номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/identify/v1/identify).
[Создание тестового неидентифицированного пополнения номинального счёта](https://developers.alfabank.ru/products/alfa-api/documentation/articles/sandbox/articles/nominal-accounts/articles/unidentified-transaction/v1/unidentified-transaction)
API Helper
Beta
---
какой email технической поддержки?
10:36
Доставлено
Для связи с технической поддержкой используйте электронную почту alfa\_api@alfabank.ru.
10:36
как происходит flow регистрации бенифициара?
10:53
Доставлено
Для регистрации бенефициара необходимо выполнить следующие шаги:
1. 1.
**Создать запрос на регистрацию бенефициара** с помощью метода:
- •
**Песочница**: `POST https://sandbox.alfabank.ru/api/na/jp/v1/beneficiaries`
- •
**Пром**: `POST https://baas.alfabank.ru/api/na/jp/v1/beneficiaries`
При этом нужно учитывать, что в зависимости от типа бенефициара поля запроса заполняются по-разному.
2. 2.
**Получить статус запроса** с помощью метода [«Получение статуса запроса»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/counterparties/articles/get-request-status/v1/get-request-status).
3. 3.
**После регистрации бенефициара необходимо указать его платёжные реквизиты** с помощью метода [«Добавление платёжных реквизитов бенефициара»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/counterparties/articles/beneficiaries/articles/add-requisites/v1/add-requisites).
При успешной регистрации бенефициару присваивается уникальный идентификатор — `beneficiaryId`.
**Важно:** учёт денежных средств на виртуальных счетах бенефициаров будет вестись корректно только при условии, что владелец номинального счёта не использует другие каналы вывода средств с номинального счёта (например, банк-клиент, платёжные поручения на бумажном носителе, сервисы Alfa API для осуществления переводов и другие).
Ссылки на документацию:
- •
[Создание запроса на регистрацию бенефициара](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/counterparties/articles/beneficiaries/articles/add-beneficiary/v1/add-beneficiary)
- •
[Получение статуса запроса](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/counterparties/articles/get-request-status/v1/get-request-status)
- •
[Добавление платёжных реквизитов бенефициара](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/counterparties/articles/beneficiaries/articles/add-requisites/v1/add-requisites)
10:53
а как же socring?
10:55
Доставлено
Для проверки актуальности ранее зарегистрированных данных о бенефициаре используются следующие методы:
1. 1.
**«Создание запроса на проверку бенефициара»** — инициирует процесс скоринга бенефициара.
2. 2.
**«Получение статуса запроса»** — позволяет отслеживать статус ранее созданного запроса на проверку.
**Ссылки на документацию:**
- •
[Создание запроса на проверку бенефициара](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/counterparties/articles/beneficiaries/articles/beneficiary-scoring/v1/beneficiary-scoring)
- •
[Получение статуса запроса](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/counterparties/articles/get-request-status/v1/get-request-status)
10:55
каким образом можно пополнить номинальный счёт компании?
22:24
Доставлено
Депонент (бенефициар) может пополнить номинальный счёт удобным ему способом. После этого владелец номинального счёта должен идентифицировать поступившие денежные средства. Для этого владельцу счёта необходимо выполнить следующие шаги:
1. 1.
**Получить информацию обо всех неидентифицированных пополнениях** с помощью метода [«Получение списка неидентифицированных пополнений номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/get-unidentified-transactions/v1/get-unidentified-transactions).
2. 2.
**Создать запрос на идентификацию денежных средств** с помощью метода [«Создание запроса на идентификацию пополнения номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/identify/v1/identify).
3. 3.
**Получить статус запроса на идентификацию денежных средств** с помощью метода [«Получение статуса запроса на идентификацию пополнения номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/get-identify-request-status/v1/get-identify-request-status).
22:25
каким образом можно пополнить номинальный счёт компании?
22:25
Доставлено
Депонент (бенефициар) может пополнить номинальный счёт удобным ему способом. После этого владелец номинального счёта должен идентифицировать поступившие денежные средства. Для этого владельцу счёта необходимо выполнить следующие шаги:
1. 1.
Получить информацию обо всех неидентифицированных пополнениях с помощью метода [«Получение списка неидентифицированных пополнений номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/get-unidentified-transactions/v1/get-unidentified-transactions).
2. 2.
Создать запрос на идентификацию денежных средств с помощью метода [«Создание запроса на идентификацию пополнения номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/identify/v1/identify).
3. 3.
Получить статус запроса на идентификацию денежных средств с помощью метода [«Получение статуса запроса на идентификацию пополнения номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/get-identify-request-status/v1/get-identify-request-status).
**Важно:** для работы с сервисом необходимо использовать электронную подпись. Допускается использование сертификата, выпущенного с помощью [сервиса «Электронная подпись»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/signature/articles/intro/intro), или сертификата УКЭП, выпущенного в любом аккредитованном УЦ.
Ссылка на статью с подробным описанием процесса: [Идентификация пополнений номинального счёта](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/intro/intro).
22:25
а что это значит "любым удобным способом"? каким например?
22:25
Доставлено
В предоставленной документации нет конкретного перечня способов пополнения номинального счёта. Однако указано, что депонент (бенефициар) может пополнить номинальный счёт удобным ему способом. После этого владелец номинального счёта должен идентифицировать поступившие денежные средства с помощью специальных методов API.
Для идентификации пополнений владелец счёта может использовать следующие методы:
1. 1.
Получить информацию обо всех неидентифицированных пополнениях: [«Получение списка неидентифицированных пополнений номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/get-unidentified-transactions/v1/get-unidentified-transactions).
2. 2.
Создать запрос на идентификацию денежных средств: [«Создание запроса на идентификацию пополнения номинального счёта»](https://developers.alfa-bank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/identify/v1/identify).
3. 3.
Получить статус запроса на идентификацию денежных средств: [«Получение статуса запроса на идентификацию пополнения номинального счёта»](https://developers.alfabank.ru/products/alfa-api/documentation/articles/nominal-accounts/articles/identify-transactions/articles/get-identify-request-status/v1/get-identify-request-status).
Для более детальной информации о возможных способах пополнения номинального счёта рекомендуется обратиться на почту alfa\_api@alfabank.ru.
22:25

View File

@@ -0,0 +1,355 @@
H2H
Песочница
POST https://sandbox.alfabank.ru/api/na/jp/v1/unidentified-transactions
##### Scope
`beneficiary-payments`
[](https://developers.alfabank.ru/assets/developers-portal.docs.alfa-api/articles/sandbox/articles/nominal-accounts/articles/unidentified-transaction/v1/files/unidentified-transaction.yaml)
##### Описание
Метод позволяет создать тестовое неидентифицированное пополнение номинального счёта.
###### Параметры запроса
Атрибут
Описание
Authorization
Accept
stringrequired
application/json
Content-Type
stringrequired
application/json
Атрибут
Описание
documentNumber
stringrequired
Номер платёжного поручения
date
stringrequired
Дата и время пополнения по МСК
amount
stringrequired
Сумма пополнения в рублях
purpose
stringrequired
Назначение платежа
accountNumber
stringrequired
Номер номинального счёта
objectrequired
Информация о плательщике
inn
stringoptional
ИНН плательщика
kpp
stringoptional
КПП плательщика
accountNumber
stringoptional
Номер счёта плательщика
name
stringoptional
Имя плательщика
bankAccount
stringoptional
Номер счёта банка плательщика
bankBik
stringoptional
БИК банка плательщика
bankName
stringoptional
Наименования банка плательщика
```bash
1curl --location 'https://baas.alfabank.ru/api/na/jp/v1/unidentified-transactions' \
2--header 'Authorization: Bearer {access-token}' \
3--header 'Content-Type: application/json' \
4--header 'Accept: application/json' \
5--data '{
6 "documentNumber": "3",
7 "date": "2026-04-03T13:23:24.439Z",
8 "amount": 200.9,
9 "purpose": "Входящая транзакция созданная вручную для тестирования на песочнице",
10 "accountNumber": "40817810306050000000",
11 "payer": {
12 "inn": "221284672706",
13 "kpp": "641914992",
14 "accountNumber": "87149728127096890679",
15 "name": "Имя плательщика",
16 "bankAccount": "3932755239052037086628",
17 "bankBik": "108997635",
18 "bankName": "Наименования банка плательщика"
19 }
20}'
```
###### Параметры ответа
```json
1{
2 "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6\"",
3 "documentNumber": "3",
4 "date": "2025-04-08T10:47:17.927Z",
5 "amount": 200.9,
6 "purpose": "Входящая транзакция созданная вручную для тестирования на песочнице",
7 "accountNumber": "40817810306050000000",
8 "payer": {
9 "inn": "221284672706",
10 "kpp": "641914992",
11 "accountNumber": "87149728127096890679",
12 "name": "Имя плательщика",
13 "bankAccount": "3932755239052037086628",
14 "bankBik": "108997635",
15 "bankName": "Наименования банка плательщика"
16 }
17}
```
атрибут
описание
id
stringrequired
Идентификатор операции пополнения
documentNumber
stringrequired
Номер платёжного поручения
date
stringrequired
Дата и время пополнения по МСК
amount
stringrequired
Сумма пополнения в рублях
purpose
stringrequired
Назначение платежа
accountNumber
stringrequired
Номер номинального счёта
objectrequired
Информация о плательщике
inn
stringoptional
ИНН плательщика
kpp
stringoptional
КПП плательщика
accountNumber
stringoptional
Номер счёта плательщика
name
stringoptional
Имя плательщика
bankAccount
stringoptional
Номер счёта банка плательщика
bankBik
stringoptional
БИК банка плательщика
bankName
stringoptional
Наименования банка плательщика
атрибут
описание
error
stringrequired
Код ошибки. Принимает возможные значения:
- •
`invalid_request` — неверные входные параметры запроса
error\_description
stringoptional
Описание ошибки
атрибут
описание
error
stringrequired
Код ошибки. Принимает возможные значения:
- •
`invalid_token` — предоставленный токен просрочен, отозван, имеет неправильный формат или недействителен по другим причинам
error\_description
stringoptional
Описание ошибки
атрибут
описание
error
stringrequired
Код ошибки. Принимает возможные значения:
- •
`insufficient_scope` — у пользователя нет прав на использование запрашиваемого сервиса, доступ к которому не предусмотрен настройками scope
error\_description
stringoptional
Описание ошибки
атрибут
описание
error
stringrequired
Код ошибки. Принимает возможные значения:
- •
`unknown_endpoint` — эндпоинт не найден или неактивен
error\_description
stringoptional
Описание ошибки
Нет тела ответа
атрибут
описание
error
stringrequired
Код ошибки. Принимает возможные значения:
- •
`internal_error` — внутренняя ошибка сервиса при выполнении обработки запроса
error\_description
stringoptional
Описание ошибки
Нет тела ответа

View File

@@ -1,49 +1,56 @@
{
"name": "apoloz/alfa-bank-sdk",
"description": "Unofficial Alfa Bank API SDK for PHP using Saloon and Laravel Data",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "apoloz",
"email": "anatoliy.poloz@gmail.com"
}
],
"require": {
"php": "^8.2",
"saloonphp/saloon": "^3.0",
"saloonphp/laravel-plugin": "^3.0",
"saloonphp/pagination-plugin": "^2.0",
"spatie/laravel-data": "^4.0",
"guzzlehttp/guzzle": "^7.0",
"phpseclib/phpseclib": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"pestphp/pest": "^2.0",
"phpstan/phpstan": "^2.1"
},
"autoload": {
"psr-4": {
"Apoloz\\AlfaBank\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Apoloz\\AlfaBank\\Laravel\\AlfaBankServiceProvider"
]
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"scripts": {
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage"
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
"name": "apoloz/alfa-bank-sdk",
"description": "Unofficial Alfa Bank API SDK for PHP using Saloon and Laravel Data",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "apoloz",
"email": "anatoliy.poloz@gmail.com"
}
],
"require": {
"php": "^8.4",
"saloonphp/saloon": "^4.0",
"saloonphp/laravel-plugin": "^4.0",
"saloonphp/pagination-plugin": "^2.0",
"spatie/laravel-data": "^4.0",
"guzzlehttp/guzzle": "^7.0",
"phpseclib/phpseclib": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"pestphp/pest": "^2.0",
"phpstan/phpstan": "^2.1"
},
"autoload": {
"psr-4": {
"Apoloz\\AlfaBank\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Apoloz\\AlfaBank\\Laravel\\AlfaBankServiceProvider"
]
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"scripts": {
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage"
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
},
"audit": {
"ignore": {
"PKSA-xnj5-w74d-6wmz": "true",
"PKSA-rnpm-45mg-w6ht": "true",
"PKSA-5szq-gvrg-ttfq": "true"
}
}
}
}

View File

@@ -29,6 +29,7 @@ class UnidentifiedTransactionData extends Data
public ?string $documentNumber = null,
public ?string $date = null,
public ?string $purpose = null,
public ?string $accountNumber = null,
public ?PayerData $payer = null, // Nested DTO for payer details
) {}
// </SECTION>

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Apoloz\AlfaBank\Requests\NominalAccounts\Identification;
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\UnidentifiedTransactionData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
class CreateUnidentifiedTransactionRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::POST;
public function __construct(
protected string $documentNumber,
protected string $date,
protected float $amount,
protected string $purpose,
protected string $accountNumber,
protected ?array $payer = null,
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/unidentified-transactions';
}
public function createDtoFromResponse(Response $response): UnidentifiedTransactionData
{
return UnidentifiedTransactionData::from($response->json());
}
protected function defaultBody(): array
{
return array_filter([
'documentNumber' => $this->documentNumber,
'date' => $this->date,
'amount' => $this->amount,
'purpose' => $this->purpose,
'accountNumber' => $this->accountNumber,
'payer' => $this->payer ?? $this->getDefaultPayer(),
], fn ($value) => $value !== null);
}
protected function getDefaultPayer(): array
{
return [
'inn' => '221284672706',
'kpp' => '641914992',
'accountNumber' => '87149728127096890679',
'name' => 'Тестовый плательщик',
'bankAccount' => '3932755239052037086628',
'bankBik' => '108997635',
'bankName' => 'АО "Альфа-Банк"',
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/*
// MODULE_CONTRACT: Defines the data structure for the response when generating an SBP QR Code.
// RESPONSIBILITY: To provide a strongly-typed structure for the API's response to QR code generation requests, including status, potential error messages, and the generated QR code data (image, payload, ID).
// INPUTS: Raw array data from the SBP QR Code generation API response.
// OUTPUTS: An instantiated DTO with properties mapped from the input array.
// LINKS: Sbp/Requests/GetQrCodeRequest.php, README.md#sbp-api
*/
namespace Apoloz\AlfaBank\Sbp\Data;
// <SECTION name="module_imports" intent="Import the base Spatie\LaravelData class.">
// <BELIEF_STATE "This class extends `Spatie\LaravelData\Data` to leverage its robust data mapping and type safety, ensuring consistent and predictable handling of SBP QR Code response payloads.">
use Spatie\LaravelData\Data;
// </SECTION>
/**
*
* "queryData":
{
"notificationUrl":"https://sbp-payments",
"SenderFIO":"Иванов|Иван|Иванович",
"SenderID":"0079123456789",
"SenderBankBIC":"44525593"
}
*
* */
// <SECTION name="class_definition" intent="Define the QR Code response data structure.">
// <BELIEF_STATE "The `QrCodeResponseData` DTO encapsulates all possible fields returned by the SBP QR Code generation API. Fields are explicitly typed as nullable to accommodate variations in API responses, particularly for error scenarios where certain data might be absent.">
class GetQRCdQueryData extends Data
{
public function __construct(
public string $notificationUrl,
public string $SenderFIO,
public string $SenderID
) {}
}

View File

@@ -54,8 +54,8 @@ class QrCodeRequestData extends Data
public ?string $width = null,
public ?string $height = null,
// <BELIEF_STATE "The `notificationUrl` specifies where the bank should send webhook notifications about payment status changes.">
public ?string $notificationUrl = null,
// <BELIEF_STATE "The `queryData` specifies where the bank should send webhook notifications about payment status changes.">
public GetQRCdQueryData $queryData,
// Dynamic QR specific fields
// <BELIEF_STATE "The `OrderNumber` is specific to dynamic QRs, providing a unique identifier for the order in the merchant's system.">
@@ -68,17 +68,6 @@ class QrCodeRequestData extends Data
// <BELIEF_STATE "Subscription service identifiers are used for recurring payment scenarios with SBP.">
public ?string $subscriptionServiceId = null,
public ?string $subscriptionServiceName = null,
// Optional user data for Dynamic QR (often part of sender identification)
// <BELIEF_STATE "Sender details are crucial for identifying the payer in certain SBP scenarios. `#[MapName]` ensures these follow API conventions.">
#[MapName('SenderFIO')]
public ?string $senderFio = null,
#[MapName('SenderID')]
public ?string $senderId = null,
#[MapName('SenderBankBIC')]
public ?string $senderBankBic = null,
) {}
// </SECTION>
}

View File

@@ -13,11 +13,12 @@ namespace Apoloz\AlfaBank\Sbp\Requests\CashLink;
// <SECTION name="module_imports" intent="Import DTO and Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request requires the `ActivateCashQrRequestData` DTO for its JSON body and utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\CashLink\ActivateCashQrRequestData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -25,7 +26,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for activating a CashLink QR Code, a key management operation in SBP payment systems.">
class ActivateCashQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Activating a resource is an action that changes the system state, so POST is the correct verb.">
@@ -56,9 +57,9 @@ class ActivateCashQrRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Fields from the DTO are mapped, including conditional parameters for order number, payment purpose, QR TTL, and notification URLs.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'ActivateCashQRc',
'TermNo' => $this->data->TermNo,
'qrcId' => $this->data->qrcId,
@@ -67,22 +68,22 @@ class ActivateCashQrRequest extends Request implements HasBody
];
if ($this->data->OrderNumber !== null) {
$body['OrderNumber'] = $this->data->OrderNumber;
$bodyArray['OrderNumber'] = $this->data->OrderNumber;
}
if ($this->data->paymentPurpose !== null) {
$body['paymentPurpose'] = $this->data->paymentPurpose;
$bodyArray['paymentPurpose'] = $this->data->paymentPurpose;
}
if ($this->data->qrTtl !== null) {
$body['qrTtl'] = $this->data->qrTtl;
$bodyArray['qrTtl'] = $this->data->qrTtl;
}
if ($this->data->redirectUrl !== null) {
$body['redirectUrl'] = $this->data->redirectUrl;
$bodyArray['redirectUrl'] = $this->data->redirectUrl;
}
if ($this->data->notificationUrl !== null) {
$body['queryData'] = ['notificationUrl' => $this->data->notificationUrl];
$bodyArray['queryData'] = ['notificationUrl' => $this->data->notificationUrl];
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,20 +12,20 @@ namespace Apoloz\AlfaBank\Sbp\Requests\CashLink;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to change the account for a CashLink QR Code.">
// <BELIEF_STATE "This class encapsulates the logic for modifying a CashLink QR Code, which is a key management operation.">
class ChangeAccountForCashQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Modifying a resource via an action is correctly represented by a POST request in the SBP API context.">
@@ -53,7 +53,7 @@ class ChangeAccountForCashQrRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
public function createDtoFromResponse(\Saloon\Http\Response $response): array
public function createDtoFromResponse(Response $response): array
{
return $response->json();
}
@@ -61,9 +61,9 @@ class ChangeAccountForCashQrRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Fields are conditionally added based on constructor parameters, ensuring the payload precisely matches the API specification.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'ChangeAccForCashQRc',
'TermNo' => $this->TermNo,
'qrcId' => $this->qrcId,
@@ -71,16 +71,16 @@ class ChangeAccountForCashQrRequest extends Request implements HasBody
];
if ($this->NewTermNo !== null) {
$body['NewTermNo'] = $this->NewTermNo;
$bodyArray['NewTermNo'] = $this->NewTermNo;
}
if ($this->NewMerchantId !== null) {
$body['NewMerchantId'] = $this->NewMerchantId;
$bodyArray['NewMerchantId'] = $this->NewMerchantId;
}
if ($this->redirectUrl !== null) {
$body['redirectUrl'] = $this->redirectUrl;
$bodyArray['redirectUrl'] = $this->redirectUrl;
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,20 +12,20 @@ namespace Apoloz\AlfaBank\Sbp\Requests\CashLink;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to change the terminal number for a CashLink QR Code.">
// <BELIEF_STATE "This class encapsulates the logic for modifying a CashLink QR Code, specifically its associated terminal, which is a key management operation.">
class ChangeTermNoForCashQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Modifying a resource via an action is correctly represented by a POST request in the SBP API context.">
@@ -49,7 +49,7 @@ class ChangeTermNoForCashQrRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
public function createDtoFromResponse(\Saloon\Http\Response $response): array
public function createDtoFromResponse(Response $response): array
{
return $response->json();
}
@@ -57,13 +57,15 @@ class ChangeTermNoForCashQrRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` and `qrcId` are included in the body to identify the target.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'ChangeTermNoForCashQRc',
'TermNo' => $this->TermNo,
'qrcId' => $this->qrcId,
];
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,20 +12,20 @@ namespace Apoloz\AlfaBank\Sbp\Requests\CashLink;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to delete a CashLink QR Code.">
// <BELIEF_STATE "This class encapsulates the logic for deleting a CashLink QR Code, which is a critical management operation.">
class DeleteCashQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Deleting a resource via an action is correctly represented by a POST request in the SBP API context.">
@@ -49,7 +49,7 @@ class DeleteCashQrRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
public function createDtoFromResponse(\Saloon\Http\Response $response): array
public function createDtoFromResponse(Response $response): array
{
return $response->json();
}
@@ -57,13 +57,15 @@ class DeleteCashQrRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` and `qrcId` are included in the body to identify the target.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'DelCashQRc',
'TermNo' => $this->TermNo,
'qrcId' => $this->qrcId,
];
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,20 +12,20 @@ namespace Apoloz\AlfaBank\Sbp\Requests\CashLink;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to get the history of a CashLink QR Code.">
// <BELIEF_STATE "This class encapsulates the logic for querying the history of a CashLink QR Code, which is useful for auditing and reconciliation.">
class GetCashQrHistoryRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP history requests are often performed via POST requests with a JSON body.">
@@ -51,7 +51,7 @@ class GetCashQrHistoryRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
public function createDtoFromResponse(\Saloon\Http\Response $response): array
public function createDtoFromResponse(Response $response): array
{
return $response->json();
}
@@ -59,15 +59,17 @@ class GetCashQrHistoryRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. All constructor properties are directly mapped to the request body to identify the QR Code and the date range.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'GetCashQrcId',
'TermNo' => $this->TermNo,
'qrcId' => $this->qrcId,
'dateFrom' => $this->dateFrom,
'dateTo' => $this->dateTo,
];
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,20 +12,20 @@ namespace Apoloz\AlfaBank\Sbp\Requests\CashLink;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to get the status of a CashLink QR Code.">
// <BELIEF_STATE "This class encapsulates the logic for querying the status of a CashLink QR Code, which is crucial for tracking payment flows.">
class GetCashQrStatusRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP status checks are often performed via POST requests with a JSON body.">
@@ -49,7 +49,7 @@ class GetCashQrStatusRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
public function createDtoFromResponse(\Saloon\Http\Response $response): array
public function createDtoFromResponse(Response $response): array
{
return $response->json();
}
@@ -57,13 +57,15 @@ class GetCashQrStatusRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` and `qrcId` identify the target.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'GetCashQRCstatus',
'TermNo' => $this->TermNo,
'qrcId' => $this->qrcId,
];
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -14,11 +14,12 @@ namespace Apoloz\AlfaBank\Sbp\Requests\CashLink;
// <BELIEF_STATE "This POST request requires specific DTOs for its JSON body and its response, along with Saloon's body-handling traits.">
use Apoloz\AlfaBank\Sbp\Data\CashLink\RegCashQrRequestData;
use Apoloz\AlfaBank\Sbp\Data\CashLink\RegCashQrResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -26,7 +27,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for generating a CashLink QR Code, a key feature in SBP payment systems.">
class RegCashQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Creating a new QR Code resource or initiating a generation action is correctly represented by a POST request.">
@@ -57,30 +58,30 @@ class RegCashQrRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Fields from the DTO are mapped, including conditional parameters for media type, dimensions, redirect URL, and QR Code ID.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'RegCashQRc',
'TermNo' => $this->data->TermNo,
];
if ($this->data->mediaType !== null) {
$body['mediaType'] = $this->data->mediaType;
$bodyArray['mediaType'] = $this->data->mediaType;
}
if ($this->data->width !== null) {
$body['width'] = $this->data->width;
$bodyArray['width'] = $this->data->width;
}
if ($this->data->height !== null) {
$body['height'] = $this->data->height;
$bodyArray['height'] = $this->data->height;
}
if ($this->data->redirectUrl !== null) {
$body['redirectUrl'] = $this->data->redirectUrl;
$bodyArray['redirectUrl'] = $this->data->redirectUrl;
}
if ($this->data->qrcId !== null) {
$body['qrcId'] = $this->data->qrcId;
$bodyArray['qrcId'] = $this->data->qrcId;
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -18,7 +18,6 @@ use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
// </SECTION>
@@ -26,7 +25,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for modifying a Digital Ruble CashLink QR Code, which is a key management operation.">
class ChangeAccForCashUniQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Modifying a resource via an action is correctly represented by a POST request in the SBP API context.">
@@ -57,42 +56,42 @@ class ChangeAccForCashUniQrRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Fields from the DTO are mapped, including conditional parameters for the new account, merchant, redirect URL, etc.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'ChangeAccForCashUniQr',
'TermNo' => $this->data->TermNo,
];
if ($this->data->messageID !== null) {
$body['messageID'] = $this->data->messageID;
$bodyArray['messageID'] = $this->data->messageID;
}
if ($this->data->qrcId !== null) {
$body['qrcId'] = $this->data->qrcId;
$bodyArray['qrcId'] = $this->data->qrcId;
}
if ($this->data->account !== null) {
$body['account'] = $this->data->account;
$bodyArray['account'] = $this->data->account;
}
if ($this->data->accountDR !== null) {
$body['accountDR'] = $this->data->accountDR;
$bodyArray['accountDR'] = $this->data->accountDR;
}
if ($this->data->NewTermNo !== null) {
$body['NewTermNo'] = $this->data->NewTermNo;
$bodyArray['NewTermNo'] = $this->data->NewTermNo;
}
if ($this->data->NewMerchantId !== null) {
$body['NewMerchantId'] = $this->data->NewMerchantId;
$bodyArray['NewMerchantId'] = $this->data->NewMerchantId;
}
if ($this->data->redirectUrl !== null) {
$body['redirectUrl'] = $this->data->redirectUrl;
$bodyArray['redirectUrl'] = $this->data->redirectUrl;
}
if ($this->data->paymentPageUrl !== null) {
$body['paymentPageUrl'] = $this->data->paymentPageUrl;
$bodyArray['paymentPageUrl'] = $this->data->paymentPageUrl;
}
if ($this->data->fraudScore !== null) {
$body['fraudScore'] = $this->data->fraudScore;
$bodyArray['fraudScore'] = $this->data->fraudScore;
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -18,7 +18,6 @@ use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
// </SECTION>
@@ -26,7 +25,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for generating a Digital Ruble CashLink QR Code, a key feature in the SBP Digital Ruble operations.">
class CreateCashUniQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Creating a new QR Code resource or initiating a generation action is correctly represented by a POST request.">
@@ -57,40 +56,40 @@ class CreateCashUniQrRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Fields from the DTO are mapped, including conditional and nested `queryData` for notification URLs.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'ReqCashUniQr',
'TermNo' => $this->data->TermNo,
];
if ($this->data->messageID !== null) {
$body['messageID'] = $this->data->messageID;
$bodyArray['messageID'] = $this->data->messageID;
}
if ($this->data->redirectUrl !== null) {
$body['redirectUrl'] = $this->data->redirectUrl;
$bodyArray['redirectUrl'] = $this->data->redirectUrl;
}
if ($this->data->paymentPageUrl !== null) {
$body['paymentPageUrl'] = $this->data->paymentPageUrl;
$bodyArray['paymentPageUrl'] = $this->data->paymentPageUrl;
}
if ($this->data->mediaType !== null) {
$body['mediaType'] = $this->data->mediaType;
$bodyArray['mediaType'] = $this->data->mediaType;
}
if ($this->data->width !== null) {
$body['width'] = $this->data->width;
$bodyArray['width'] = $this->data->width;
}
if ($this->data->height !== null) {
$body['height'] = $this->data->height;
$bodyArray['height'] = $this->data->height;
}
if ($this->data->qrcId !== null) {
$body['qrcId'] = $this->data->qrcId;
$bodyArray['qrcId'] = $this->data->qrcId;
}
if ($this->data->notificationUrl !== null) {
$body['queryData'] = ['notificationUrl' => $this->data->notificationUrl];
$bodyArray['queryData'] = ['notificationUrl' => $this->data->notificationUrl];
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -18,7 +18,6 @@ use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
// </SECTION>
@@ -26,7 +25,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for querying the status of a Digital Ruble CashLink QR Code, which is crucial for tracking payment flows.">
class GetCashUniQrStatusRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP status checks are often performed via POST requests with a JSON body.">
@@ -57,9 +56,9 @@ class GetCashUniQrStatusRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo`, `uniQrId`, and `paramsId` identify the target. Optional fields like `messageID` and `payrrn` provide additional context.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'GetCashUniQrStatus',
'TermNo' => $this->data->TermNo,
'uniQrId' => $this->data->uniQrId,
@@ -67,13 +66,13 @@ class GetCashUniQrStatusRequest extends Request implements HasBody
];
if ($this->data->messageID !== null) {
$body['messageID'] = $this->data->messageID;
$bodyArray['messageID'] = $this->data->messageID;
}
if ($this->data->payrrn !== null) {
$body['payrrn'] = $this->data->payrrn;
$bodyArray['payrrn'] = $this->data->payrrn;
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -6,15 +6,16 @@ namespace Apoloz\AlfaBank\Sbp\Requests\DigitalRuble;
use Apoloz\AlfaBank\Sbp\Data\DigitalRuble\CreateUniQrRequestData;
use Apoloz\AlfaBank\Sbp\Data\DigitalRuble\UniQrResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
class CreateUniQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
protected Method $method = Method::POST;
@@ -32,40 +33,40 @@ class CreateUniQrRequest extends Request implements HasBody
return UniQrResponseData::from($response->json());
}
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'ReqUniQr',
'TermNo' => $this->data->TermNo,
'uniQrType' => $this->data->uniQrType,
];
if ($this->data->amount !== null) {
$body['amount'] = $this->data->amount;
$bodyArray['amount'] = $this->data->amount;
}
if ($this->data->paymentPurpose !== null) {
$body['paymentPurpose'] = $this->data->paymentPurpose;
$bodyArray['paymentPurpose'] = $this->data->paymentPurpose;
}
if ($this->data->redirectUrl !== null) {
$body['redirectUrl'] = $this->data->redirectUrl;
$bodyArray['redirectUrl'] = $this->data->redirectUrl;
}
if ($this->data->paymentPageUrl !== null) {
$body['paymentPageUrl'] = $this->data->paymentPageUrl;
$bodyArray['paymentPageUrl'] = $this->data->paymentPageUrl;
}
if ($this->data->mediaType !== null) {
$body['mediaType'] = $this->data->mediaType;
$bodyArray['mediaType'] = $this->data->mediaType;
}
if ($this->data->width !== null) {
$body['width'] = $this->data->width;
$bodyArray['width'] = $this->data->width;
}
if ($this->data->height !== null) {
$body['height'] = $this->data->height;
$bodyArray['height'] = $this->data->height;
}
if ($this->data->qrTtl !== null) {
$body['qrTtl'] = $this->data->qrTtl;
$bodyArray['qrTtl'] = $this->data->qrTtl;
}
if ($this->data->messageID !== null) {
$body['messageID'] = $this->data->messageID;
$bodyArray['messageID'] = $this->data->messageID;
}
$queryData = [];
@@ -83,9 +84,9 @@ class CreateUniQrRequest extends Request implements HasBody
}
if (! empty($queryData)) {
$body['queryData'] = $queryData;
$bodyArray['queryData'] = $queryData;
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
}

View File

@@ -12,20 +12,21 @@ namespace Apoloz\AlfaBank\Sbp\Requests\DigitalRuble;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\DigitalRuble\GetUniQrStatusResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to get the status of Universal QR Codes.">
// <BELIEF_STATE "This class encapsulates the logic for querying the status of multiple Uni QR Codes, which is part of managing digital ruble transactions.">
class GetUniQrStatusRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP status checks are often performed via POST requests with a JSON body.">
@@ -49,21 +50,23 @@ class GetUniQrStatusRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
// <BELIEF_STATE "Casting the response to a `GetUniQrStatusResponseData` DTO provides a type-safe and structured object for the status of Universal QR Codes.">
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\DigitalRuble\GetUniQrStatusResponseData
public function createDtoFromResponse(Response $response): GetUniQrStatusResponseData
{
return \Apoloz\AlfaBank\Sbp\Data\DigitalRuble\GetUniQrStatusResponseData::from($response->json());
return GetUniQrStatusResponseData::from($response->json());
}
// </SECTION>
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` and `uniQrIds` are included in the body to identify the targets.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'GetUniQrStatus',
'TermNo' => $this->TermNo,
'uniQrIds' => $this->uniQrIds,
];
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -13,20 +13,21 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Management;
// <SECTION name="module_imports" intent="Import DTO and Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request requires the `BlockQrRequestData` DTO for its JSON body and utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\Management\BlockQrRequestData;
use Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to block a QR Code.">
// <BELIEF_STATE "This class encapsulates the logic for blocking an SBP QR Code, which is a critical management operation.">
class BlockQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Blocking a resource is an action that changes the system state, so POST is the correct verb.">
@@ -49,20 +50,22 @@ class BlockQrRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
// <BELIEF_STATE "Casting the response to a `SbpManagementResponseData` DTO provides a structured object for the result of the block operation.">
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData
public function createDtoFromResponse(Response $response): SbpManagementResponseData
{
return \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData::from($response->json());
return SbpManagementResponseData::from($response->json());
}
// </SECTION>
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Using the DTO's `toArray` method ensures the payload is correctly structured.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return array_merge(
$bodyArray = array_merge(
['command' => 'BlockQRcId'],
$this->data->toArray()
);
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,20 +12,21 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Management;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\Management\GetBlockedQrListResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to get a list of blocked QR Codes.">
// <BELIEF_STATE "This class encapsulates the logic for querying blocked QR Codes, which is part of managing payment terminals.">
class GetBlockedQrListRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP list requests are often performed via POST requests with a JSON body.">
@@ -48,20 +49,22 @@ class GetBlockedQrListRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
// <BELIEF_STATE "Casting the response to a `GetBlockedQrListResponseData` DTO provides a type-safe object for the list of blocked QR Codes.">
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Management\GetBlockedQrListResponseData
public function createDtoFromResponse(Response $response): GetBlockedQrListResponseData
{
return \Apoloz\AlfaBank\Sbp\Data\Management\GetBlockedQrListResponseData::from($response->json());
return GetBlockedQrListResponseData::from($response->json());
}
// </SECTION>
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` is included in the body to identify the terminal.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'GetBlockedQRcIdList',
'TermNo' => $this->TermNo,
];
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -13,20 +13,21 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Management;
// <SECTION name="module_imports" intent="Import DTO and Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request requires the `BlockQrRequestData` DTO for its JSON body and utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\Management\BlockQrRequestData;
use Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to unblock a QR Code.">
// <BELIEF_STATE "This class encapsulates the logic for unblocking an SBP QR Code, which is a critical management operation.">
class UnblockQrRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Unblocking a resource is an action that changes the system state, so POST is the correct verb.">
@@ -49,20 +50,22 @@ class UnblockQrRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
// <BELIEF_STATE "Casting the response to a `SbpManagementResponseData` DTO provides a structured object for the result of the unblock operation.">
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData
public function createDtoFromResponse(Response $response): SbpManagementResponseData
{
return \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData::from($response->json());
return SbpManagementResponseData::from($response->json());
}
// </SECTION>
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Using the DTO's `toArray` method ensures the payload is correctly structured.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return array_merge(
$bodyArray = array_merge(
['command' => 'UnblockQRcId'],
$this->data->toArray()
);
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -13,11 +13,12 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Registry;
// <SECTION name="module_imports" intent="Import DTO and Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request requires the `GetBankListResponseData` DTO for its response and utilizes `HasBody` and `HasJsonBody` for proper payload serialization.">
use Apoloz\AlfaBank\Sbp\Data\Registry\GetBankListResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -25,7 +26,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for querying the list of SBP participating banks, which is essential for routing payments.">
class GetBankListRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP list requests are often performed via POST requests with a JSON body.">
@@ -57,20 +58,20 @@ class GetBankListRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Optional fields are conditionally added to the body.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'GetBankList',
];
if ($this->messageID !== null) {
$body['messageID'] = $this->messageID;
$bodyArray['messageID'] = $this->messageID;
}
if ($this->BankMemberIdFilter !== null) {
$body['BankMemberIdFilter'] = $this->BankMemberIdFilter;
$bodyArray['BankMemberIdFilter'] = $this->BankMemberIdFilter;
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -14,11 +14,12 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Reversal;
// <BELIEF_STATE "This POST request requires specific DTOs for its JSON body and its response, along with Saloon's body-handling traits.">
use Apoloz\AlfaBank\Sbp\Data\Reversal\GetQrcReversalDataRequestData;
use Apoloz\AlfaBank\Sbp\Data\Reversal\GetQrcReversalDataResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -26,7 +27,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for querying detailed SBP reversal information, which is crucial for auditing and reconciliation.">
class GetQrcReversalDataRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP data requests are often performed via POST requests with a JSON body.">
@@ -57,12 +58,14 @@ class GetQrcReversalDataRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Other fields from the DTO are conditionally added, ensuring the payload precisely matches the API specification.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return array_merge(
$bodyArray = array_merge(
['command' => 'GetQRCreversalData'],
$this->data->toArray()
);
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -5,15 +5,16 @@ declare(strict_types=1);
namespace Apoloz\AlfaBank\Sbp\Requests\Reversal;
use Apoloz\AlfaBank\Sbp\Data\Reversal\ReversalHistoryResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
class GetQrcReversalHistoryRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
protected Method $method = Method::POST;
@@ -32,12 +33,14 @@ class GetQrcReversalHistoryRequest extends Request implements HasBody
return ReversalHistoryResponseData::from($response->json());
}
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'GetQRCreversalHistory',
'TermNo' => $this->TermNo,
'payrrn' => $this->payrrn,
];
return CmsSigner::canonicalize($bodyArray);
}
}

View File

@@ -14,11 +14,12 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Reversal;
// <BELIEF_STATE "This POST request requires specific DTOs for its JSON body and its response, along with Saloon's body-handling traits.">
use Apoloz\AlfaBank\Sbp\Data\Reversal\GetQrcReversalStatusRequestData;
use Apoloz\AlfaBank\Sbp\Data\Reversal\GetQrcReversalStatusResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -26,7 +27,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for querying the status of an SBP reversal, which is a critical operation in payment systems.">
class GetQrcReversalStatusRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP status checks are often performed via POST requests with a JSON body.">
@@ -57,12 +58,14 @@ class GetQrcReversalStatusRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Other fields from the DTO are conditionally added, ensuring the payload precisely matches the API specification.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return array_merge(
$bodyArray = array_merge(
['command' => 'GetQRCreversalStatus'],
$this->data->toArray()
);
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -14,11 +14,12 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Reversal;
// <BELIEF_STATE "This POST request requires specific DTOs for its JSON body and its response, along with Saloon's body-handling traits.">
use Apoloz\AlfaBank\Sbp\Data\Reversal\QrcReversalRequestData;
use Apoloz\AlfaBank\Sbp\Data\Reversal\QrcReversalResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -26,7 +27,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for initiating an SBP reversal, which is a critical operation in payment systems.">
class QrcReversalRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Initiating a new transaction or action that changes the system state (like a reversal) is correctly represented by a POST request.">
@@ -57,12 +58,14 @@ class QrcReversalRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Other fields from the DTO are conditionally added, ensuring the payload precisely matches the API specification.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return array_merge(
$bodyArray = array_merge(
['command' => 'QRCreversal'],
$this->data->toArray()
);
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -14,11 +14,12 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Status;
// <BELIEF_STATE "This POST request requires specific DTOs for its JSON body and its response, along with Saloon's body-handling traits.">
use Apoloz\AlfaBank\Sbp\Data\Status\GetQrcStatusRequestData;
use Apoloz\AlfaBank\Sbp\Data\Status\GetQrcStatusResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -26,7 +27,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for querying the status of an SBP QR Code, which is crucial for tracking payment flows.">
class GetQrcStatusRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP status checks are often performed via POST requests with a JSON body.">
@@ -57,12 +58,14 @@ class GetQrcStatusRequest extends Request implements HasBody
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` is mandatory, and `qrcId`, `payrrn`, and `messageID` are optional filters to pinpoint the transaction.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return array_merge(
$bodyArray = array_merge(
['command' => 'GetQRCstatus'],
$this->data->toArray()
);
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,20 +12,21 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Subscription;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\Subscription\GetQrSubStatusResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasStringBody;
// <BELIEF_STATE "The `Response` object is fully qualified in `createDtoFromResponse` as it is not explicitly imported with a `use` statement at the top. This is acceptable but less explicit.">
// use Saloon\Http\Response; // Explicit import would be cleaner
// </SECTION>
// <SECTION name="class_definition" intent="Define the request to get the status of a QR Code subscription.">
// <BELIEF_STATE "This class encapsulates the logic for querying the status of an SBP subscription, which is crucial for managing recurring payments.">
class GetQrSubStatusRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Even though it retrieves data, SBP status checks are often performed via POST requests with a JSON body.">
@@ -50,27 +51,27 @@ class GetQrSubStatusRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
// <BELIEF_STATE "Casting the response to a `GetQrSubStatusResponseData` DTO provides a structured object for the status of the subscription.">
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Subscription\GetQrSubStatusResponseData
public function createDtoFromResponse(Response $response): GetQrSubStatusResponseData
{
return \Apoloz\AlfaBank\Sbp\Data\Subscription\GetQrSubStatusResponseData::from($response->json());
return GetQrSubStatusResponseData::from($response->json());
}
// </SECTION>
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Other fields are conditionally added, ensuring the payload precisely matches the API specification.">
protected function defaultBody(): array
protected function defaultBody(): string
{
$body = [
$bodyArray = [
'command' => 'GetQRCSubStatus',
'TermNo' => $this->TermNo,
'qrcId' => $this->qrcId,
];
if ($this->RQperiod !== null) {
$body['RQperiod'] = $this->RQperiod;
$bodyArray['RQperiod'] = $this->RQperiod;
}
return $body;
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -12,11 +12,13 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Subscription;
// <SECTION name="module_imports" intent="Import Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\Subscription\QrcSubPayPetitionResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -24,7 +26,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for initiating a payment through an SBP subscription, which is a key part of recurring payment flows.">
class QrcSubPayPetitionRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Initiating a payment action that changes the system state is correctly represented by a POST request.">
@@ -50,23 +52,25 @@ class QrcSubPayPetitionRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
// <BELIEF_STATE "Casting the response to a `QrcSubPayPetitionResponseData` DTO provides a structured object for the result of the payment petition.">
public function createDtoFromResponse(Response $response): \Apoloz\AlfaBank\Sbp\Data\Subscription\QrcSubPayPetitionResponseData
public function createDtoFromResponse(Response $response): QrcSubPayPetitionResponseData
{
return \Apoloz\AlfaBank\Sbp\Data\Subscription\QrcSubPayPetitionResponseData::from($response->json());
return QrcSubPayPetitionResponseData::from($response->json());
}
// </SECTION>
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. All constructor properties are directly mapped to the request body.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return [
$bodyArray = [
'command' => 'QRCSubPayPetition',
'TermNo' => $this->TermNo,
'subscriptionToken' => $this->subscriptionToken,
'SenderMemberId' => $this->SenderMemberId,
'qrcId' => $this->qrcId,
];
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -13,11 +13,13 @@ namespace Apoloz\AlfaBank\Sbp\Requests\Subscription;
// <SECTION name="module_imports" intent="Import DTO and Saloon components for a JSON body request.">
// <BELIEF_STATE "This POST request requires the `RegQrSubRequestData` DTO for its JSON body and utilizes `HasBody` and `HasJsonBody` for proper payload serialization. It currently returns a raw array response.">
use Apoloz\AlfaBank\Sbp\Data\Subscription\RegQrSubRequestData;
use Apoloz\AlfaBank\Sbp\Data\Subscription\RegQrSubResponseData;
use Apoloz\AlfaBank\Utils\CmsSigner;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasStringBody;
// </SECTION>
@@ -25,7 +27,7 @@ use Saloon\Traits\Body\HasJsonBody;
// <BELIEF_STATE "This class encapsulates the logic for initiating an SBP subscription, which is crucial for recurring payment models.">
class RegQrSubRequest extends Request implements HasBody
{
use HasJsonBody;
use HasStringBody;
// <SECTION name="http_method" intent="Declare the HTTP verb.">
// <BELIEF_STATE "Registering a new resource or initiating an action that changes the system state is correctly represented by a POST request.">
@@ -48,20 +50,22 @@ class RegQrSubRequest extends Request implements HasBody
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
// <BELIEF_STATE "Casting the response to a `RegQrSubResponseData` DTO provides a structured object for the result of the subscription registration.">
public function createDtoFromResponse(Response $response): \Apoloz\AlfaBank\Sbp\Data\Subscription\RegQrSubResponseData
public function createDtoFromResponse(Response $response): RegQrSubResponseData
{
return \Apoloz\AlfaBank\Sbp\Data\Subscription\RegQrSubResponseData::from($response->json());
return RegQrSubResponseData::from($response->json());
}
// </SECTION>
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Using the DTO's `toArray` method ensures the payload is correctly structured.">
protected function defaultBody(): array
protected function defaultBody(): string
{
return array_merge(
$bodyArray = array_merge(
['command' => 'RegQRcSub'],
$this->data->toArray()
);
return CmsSigner::canonicalize($bodyArray);
}
// </SECTION>
}

View File

@@ -35,14 +35,14 @@ class OpenSslCmsSigner implements SignerInterface
file_put_contents($inFile, $data);
// Используем PKCS7_ATTACHED согласно документации Альфа-Банка
// Используем PKCS7_DETACHED согласно документации Альфа-Банка для digestSignatures
$status = openssl_pkcs7_sign(
$inFile,
$outFile,
$cert,
$privateKey,
[],
PKCS7_BINARY // По умолчанию это ATTACHED, если не передан PKCS7_DETACHED
PKCS7_BINARY | PKCS7_DETACHED
);
if (!$status) {
@@ -55,15 +55,17 @@ class OpenSslCmsSigner implements SignerInterface
@unlink($inFile);
@unlink($outFile);
// Извлекаем Base64 тело подписи согласно примеру из документации Альфы
$parts = explode("\n\n", $signedData, 2);
if (isset($parts[1])) {
$signature = trim($parts[1]);
// Удаляем возможные PEM-завершители и переносы строк
$signature = str_replace(["-----BEGIN PKCS7-----", "-----END PKCS7-----", "\n", "\r", " "], '', $signature);
return $signature;
// Надежно извлекаем только Base64 блок подписи из S/MIME или PEM структуры
$parts = explode("\n\n", $signedData);
foreach ($parts as $part) {
// Очищаем часть от пробелов и переносов
$cleaned = str_replace(["\n", "\r", ' '], '', $part);
// Ищем блок, который выглядит как длинный Base64 (больше 128 символов) и не содержит S/MIME заголовков
if (preg_match('/^[a-zA-Z0-9\/+]+=*$/', $cleaned) && strlen($cleaned) > 128) {
return $cleaned;
}
}
throw new SignerException("Failed to extract signature body from CMS container.");
throw new SignerException("Failed to extract signature body from CMS container. Output: " . substr($signedData, 0, 100));
}
}

View File

@@ -43,7 +43,8 @@ trait HasAlfaErrorHandling
if (str_contains($contentType, 'application/json')) {
$data = $response->json();
$errorCode = $data['error'] ?? $data['ErrorCode'] ?? 'HTTP_' . $response->status();
$errorDescription = $data['error_description'] ?? $data['message'] ?? 'Request failed';
$rawDescription = $data['error_description'] ?? $data['message'] ?? 'Request failed';
$errorDescription = $this->decodeBankMessage($rawDescription);
} else {
$errorCode = 'HTTP_' . $response->status();
$errorDescription = 'Request failed with status ' . $response->status();
@@ -55,4 +56,15 @@ trait HasAlfaErrorHandling
"({$requestId}) {$errorDescription}"
);
}
private function decodeBankMessage(string $message): string
{
$decoded = preg_replace_callback(
'/%u([0-9A-Fa-f]{4})/',
fn ($m) => mb_convert_encoding(pack('H*', $m[1]), 'UTF-8', 'UCS-2BE'),
$message
);
return urldecode($decoded);
}
}

View File

@@ -20,7 +20,7 @@ trait SignableData
// 3. Canonicalize (RFC 8785 - simple version: sort keys, remove nulls, no spaces)
$canonicalData = $this->canonicalizeArray($data);
$jsonToSign = json_encode($canonicalData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// 4. Sign
@@ -32,7 +32,7 @@ trait SignableData
}
$current = array_values($this->digestSignatures->toCollection()->all());
$current[] = new DigestSignatureData($signature, 'rsa');
$current[] = new DigestSignatureData($signature, 'RSA');
$this->digestSignatures = new DataCollection(DigestSignatureData::class, $current);
@@ -42,7 +42,7 @@ trait SignableData
private function canonicalizeArray(array $array): array
{
// Remove nulls and empty strings
$array = array_filter($array, fn($v) => $v !== null);
$array = array_filter($array, fn ($v) => $v !== null);
// Sort keys lexically
ksort($array);