forked from apoloz/alfa-bank-sdk
doc(exmaples): for nominatl
This commit is contained in:
339
README.md
339
README.md
@@ -1,194 +1,217 @@
|
||||
# Alfa Bank SDK (Unofficial)
|
||||
|
||||
This is an unofficial PHP SDK for the [Alfa Bank API](https://developers.alfabank.ru/), built using [Saloon](https://docs.saloon.dev) and [Spatie Laravel Data](https://spatie.be/docs/laravel-data).
|
||||
[](https://github.com/apoloz/alfa-bank-sdk/actions)
|
||||
[](https://phpstan.org/)
|
||||
|
||||
## Requirements
|
||||
Профессиональная PHP-библиотека для интеграции с API Альфа-Банка (BaaS и СБП). Построена на базе [Saloon v3](https://docs.saloon.dev) и [Spatie Laravel Data](https://spatie.be/docs/laravel-data).
|
||||
|
||||
## Требования
|
||||
|
||||
- PHP 8.2+
|
||||
- OpenSSL (for certificate generation/handling)
|
||||
- Расширение OpenSSL
|
||||
- [Saloon](https://docs.saloon.dev) v3.0+
|
||||
- [Spatie Laravel Data](https://spatie.be/docs/laravel-data)
|
||||
- [phpseclib](https://phpseclib.org/) (for Card Encryption)
|
||||
|
||||
## Installation
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
composer require apoloz/alfa-bank-sdk
|
||||
```
|
||||
|
||||
## Architecture & Data Flow
|
||||
---
|
||||
|
||||
The SDK abstracts complex banking protocols into a clean, object-oriented interface.
|
||||
## Архитектура и потоки данных
|
||||
|
||||
### 1. Unified Request Architecture
|
||||
Every request follows a strict "Input DTO -> Request -> Connector -> Output DTO" pipeline.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
User[Client App] --> DTO_IN[Request DTO]
|
||||
DTO_IN --> REQ[Request Class]
|
||||
REQ --> CON[Connector]
|
||||
CON -->|mTLS + Signature| API((Alfa Bank))
|
||||
API -->|Raw JSON| CON
|
||||
CON --> REQ
|
||||
REQ -->|Data Mapping| DTO_OUT[Response DTO/Collection]
|
||||
DTO_OUT --> User
|
||||
|
||||
style CON fill:#f96,stroke:#333,stroke-width:2px
|
||||
style DTO_IN fill:#bbf,stroke:#333
|
||||
style DTO_OUT fill:#9f6,stroke:#333
|
||||
```
|
||||
|
||||
### 2. Authentication & Security Flow (BaaS)
|
||||
The BaaS API requires a two-step handshake: mTLS for the tunnel and OAuth 2.0 for authorization.
|
||||
Библиотека обеспечивает строгую типизацию на всех этапах. Вы работаете только с объектами (DTO), а SDK берет на себя всю сложность протоколов банка.
|
||||
|
||||
### 1. Жизненный цикл запроса (BaaS)
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Client Application
|
||||
participant SDK as AlfaBank SDK
|
||||
participant API as Alfa Bank Gateway
|
||||
participant App as Приложение
|
||||
participant DTO as Request DTO
|
||||
participant SDK as Connector
|
||||
participant API as Alfa Bank
|
||||
|
||||
Note over App, API: 1. Connection (mTLS)
|
||||
SDK->>API: Client Hello (X.509 Certificate)
|
||||
API-->>SDK: Server Hello & Encrypted Tunnel established
|
||||
|
||||
Note over App, API: 2. Authorization (OAuth 2.0)
|
||||
App->>SDK: login(id, secret)
|
||||
SDK->>API: POST /token (Client Credentials)
|
||||
API-->>SDK: Access Token (JWT)
|
||||
SDK->>SDK: Store Token in Authenticator
|
||||
|
||||
Note over App, API: 3. Business Requests
|
||||
App->>SDK: send(CreatePaymentRequest)
|
||||
SDK->>API: POST /payments (Header: Bearer Token)
|
||||
API-->>SDK: 201 Created
|
||||
App->>DTO: 1. Создание данных
|
||||
App->>SDK: 2. send(Request)
|
||||
SDK->>SDK: 3. Маппинг полей (PascalCase)
|
||||
SDK->>SDK: 4. Подпись/Шифрование
|
||||
SDK->>API: 5. HTTPS + mTLS
|
||||
API-->>SDK: 6. JSON Ответ
|
||||
SDK->>SDK: 7. Валидация ошибок
|
||||
SDK->>App: 8. Возврат Response DTO
|
||||
```
|
||||
|
||||
### 3. SBP Security (JCS & Body Signing)
|
||||
Unlike BaaS, SBP requires signing the actual JSON body using a recursive key-sorting algorithm (JCS).
|
||||
### 2. Безопасность СБП (Body Signing)
|
||||
Для СБП реализована автоматическая каноникализация JSON (JCS) с рекурсивной сортировкой ключей перед подписью RSA-SHA256.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Raw Data (Array)"] -->|Recursive ksort| B("Canonical Array")
|
||||
B -->|JSON_UNESCAPED_UNICODE| C("JCS JSON String")
|
||||
C -->|RSA-SHA256 Sign| D("Binary Signature")
|
||||
D -->|Base64 Encode| E["'Authorization' Header"]
|
||||
E -->|HTTPS + mTLS| F((Alfa Bank SBP Gateway))
|
||||
---
|
||||
|
||||
subgraph SDK Automatics
|
||||
B
|
||||
C
|
||||
D
|
||||
end
|
||||
```
|
||||
## Настройка и Безопасность
|
||||
|
||||
### 4. Webhook Pipeline (Laravel Integration)
|
||||
The SDK acts as a security firewall for incoming bank notifications.
|
||||
Альфа-Банк требует **два набора ключей**:
|
||||
1. **Транспортные (mTLS):** Для установки защищенного соединения.
|
||||
2. **Прикладные (Signing):** Для цифровой подписи реестров и платежей.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Bank as Alfa Bank
|
||||
participant Mid as VerifyAlfaWebhook (Middleware)
|
||||
participant Fact as WebhookFactory
|
||||
participant Event as AlfaWebhookReceived (Event)
|
||||
participant Listener as Your Listener
|
||||
### Быстрый старт (Laravel)
|
||||
|
||||
Bank->>Mid: POST /webhooks/alfa
|
||||
Mid->>Mid: 1. Validate Credentials
|
||||
alt Authorized
|
||||
Mid->>Fact: 2. Parse Raw JSON
|
||||
Fact->>Fact: 3. Detect Type (BaaS/SBP)
|
||||
Fact->>Event: 4. Dispatch(Typed DTO)
|
||||
Event->>Listener: 5. Handle Business Logic
|
||||
Mid-->>Bank: 200 OK
|
||||
else Unauthorized
|
||||
Mid-->>Bank: 401 Unauthorized
|
||||
end
|
||||
```
|
||||
|
||||
## Security & Setup (Crucial)
|
||||
|
||||
Alfa Bank uses a comprehensive security model requiring **two distinct sets of keys**:
|
||||
1. **Transport (mTLS):** To establish the SSL connection.
|
||||
2. **Signing (JWS/CMS):** To digitally sign the request body.
|
||||
|
||||
### Where to get keys?
|
||||
- **Sandbox:** Provided by the bank support or downloaded from the Developer Portal.
|
||||
- **Production:**
|
||||
- **Transport (mTLS):** Generate a private key and CSR using OpenSSL. Send the CSR to `alfa_api@alfabank.ru`.
|
||||
- **Signing:** Generated by you (the client). Register the public certificate in "Alfa Business Online".
|
||||
|
||||
### 1. Converting PFX to PEM
|
||||
If the bank provided a `.pfx` file for signing, convert it to PEM:
|
||||
1. Опубликуйте конфиг:
|
||||
```bash
|
||||
# Extract Private Key
|
||||
openssl pkcs12 -in signing.pfx -nocerts -out signing_key.pem -nodes
|
||||
|
||||
# Extract Public Certificate
|
||||
openssl pkcs12 -in signing.pfx -clcerts -nokeys -out signing_cert.pem
|
||||
php artisan vendor:publish --tag=alfa-bank-config
|
||||
```
|
||||
|
||||
### 2. Initialization
|
||||
```php
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: '/path/to/transport_cert.pem',
|
||||
privateKeyPath: '/path/to/transport_key.pem',
|
||||
signingCertificatePath: '/path/to/signing_cert.pem',
|
||||
signingPrivateKeyPath: '/path/to/signing_key.pem',
|
||||
environment: Environment::SANDBOX
|
||||
);
|
||||
2. Настройте `.env`:
|
||||
```env
|
||||
ALFA_BAAS_CERT_PATH=/path/to/transport.crt
|
||||
ALFA_BAAS_KEY_PATH=/path/to/transport.key
|
||||
ALFA_BAAS_CLIENT_ID=your_id
|
||||
ALFA_BAAS_CLIENT_SECRET=your_secret
|
||||
```
|
||||
|
||||
## Core Usage
|
||||
|
||||
### Making Requests
|
||||
|
||||
#### SBP: Generate QR Code
|
||||
```php
|
||||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||||
use Apoloz\AlfaBank\Sbp\Data\QrCodeRequestData;
|
||||
|
||||
$response = $sbp->send(new GetQrCodeRequest(
|
||||
new QrCodeRequestData(TermNo: '...', amount: '100')
|
||||
));
|
||||
|
||||
$link = $response->dto()->payload;
|
||||
```
|
||||
|
||||
#### Standard Payments
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\Payments\CreatePaymentRequest;
|
||||
use Apoloz\AlfaBank\Data\Payments\PaymentData;
|
||||
|
||||
$payload = new PaymentData(amount: 100.50, ...);
|
||||
$response = $baas->send(new CreatePaymentRequest($payload));
|
||||
echo $response->dto()->id;
|
||||
```
|
||||
|
||||
### Advanced Features
|
||||
|
||||
#### Pagination
|
||||
The SDK supports automatic pagination for all list endpoints:
|
||||
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest;
|
||||
|
||||
$paginator = $baas->paginate(new GetAccountsRequest());
|
||||
|
||||
foreach ($paginator->items() as $account) {
|
||||
echo $account->number; // Typed AccountData object
|
||||
}
|
||||
```
|
||||
|
||||
#### Diagnostics
|
||||
Run the built-in Artisan command to verify your configuration:
|
||||
3. Проверьте соединение:
|
||||
```bash
|
||||
php artisan alfa:check
|
||||
```
|
||||
|
||||
## License
|
||||
---
|
||||
|
||||
MIT
|
||||
## Примеры использования
|
||||
|
||||
### 1. Авторизация и получение счетов
|
||||
```php
|
||||
use Apoloz\AlfaBank\Laravel\Facades\AlfaBank;
|
||||
use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest;
|
||||
|
||||
// SDK сам получит токен и применит его
|
||||
$accounts = AlfaBank::send(new GetAccountsRequest());
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
echo $account->number . ": " . $account->openDate;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Стандартный платеж (BaaS)
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\Payments\CreatePaymentRequest;
|
||||
use Apoloz\AlfaBank\Data\Payments\PaymentData;
|
||||
|
||||
$data = new PaymentData(
|
||||
amount: 1500.00,
|
||||
date: date('Y-m-d'),
|
||||
externalId: 'INV-2026-001',
|
||||
operationCode: '01',
|
||||
payeeAccount: '40702...',
|
||||
payeeBankBic: '044525593',
|
||||
payeeName: 'ООО Партнер',
|
||||
payerAccount: '40702...',
|
||||
payerBankBic: '044525593',
|
||||
payerBankCorrAccount: '30101...',
|
||||
payerInn: '7700000000',
|
||||
payerName: 'Моя Компания',
|
||||
priority: '5',
|
||||
purpose: 'Оплата по счету №1'
|
||||
);
|
||||
|
||||
$response = AlfaBank::send(new CreatePaymentRequest($data));
|
||||
echo "Платеж создан: " . $response->id;
|
||||
```
|
||||
|
||||
### 3. СБП: Генерация QR-кода
|
||||
```php
|
||||
use Apoloz\AlfaBank\Laravel\Facades\Sbp;
|
||||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||||
use Apoloz\AlfaBank\Sbp\Data\QrCodeRequestData;
|
||||
|
||||
$payload = new QrCodeRequestData(
|
||||
termNo: '90080567',
|
||||
amount: '10000', // 100.00 руб
|
||||
paymentPurpose: 'Заказ №5'
|
||||
);
|
||||
|
||||
$response = Sbp::send(new GetQrCodeRequest($payload));
|
||||
echo "Отсканируйте код: " . $response->payload;
|
||||
```
|
||||
|
||||
### 4. Работа с выписками (Пагинация)
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\Statement\GetTransactionsRequest;
|
||||
|
||||
// Автоматическая итерация через все страницы
|
||||
$paginator = AlfaBank::paginate(new GetTransactionsRequest(
|
||||
accountNumber: '40702...',
|
||||
executeDateFrom: '2026-01-01',
|
||||
executeDateTo: '2026-01-18'
|
||||
));
|
||||
|
||||
foreach ($paginator->items() as $transaction) {
|
||||
echo "Транзакция {" . $transaction->uuid . ": " . $transaction->amount['amount'] . " RUB\n";
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Самозанятые (Справочники и выплаты)
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\SelfEmployed\GetActivitiesRequest;
|
||||
|
||||
// Получение списка разрешенных видов деятельности
|
||||
$activities = AlfaBank::send(new GetActivitiesRequest());
|
||||
|
||||
foreach ($activities as $activity) {
|
||||
echo "{$activity->code}: {$activity->name}\n";
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Номинальные счета (Полный цикл)
|
||||
Сценарий создания бенефициара и выплаты с подписью:
|
||||
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\CreateBeneficiaryRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Payouts\CreatePayoffRequest;
|
||||
|
||||
// 1. Создание бенефициара с CMS подписью
|
||||
$response = $baas->send(new CreateBeneficiaryRequest($beneficiaryData));
|
||||
|
||||
// 2. Инициирование выплаты
|
||||
$payoff = $baas->send(new CreatePayoffRequest($payoffData));
|
||||
echo "Статус выплаты: " . $payoff->dto()->status;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Продвинутые возможности
|
||||
|
||||
### Диагностика без Laravel
|
||||
```php
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
|
||||
$ok = AlfaBank::check(
|
||||
certificatePath: 'cert.pem',
|
||||
privateKeyPath: 'key.pem',
|
||||
clientId: '...',
|
||||
clientSecret: '...'
|
||||
);
|
||||
```
|
||||
|
||||
### Логирование с маскировкой данных
|
||||
SDK автоматически скрывает номера карт, токены и подписи в логах.
|
||||
```php
|
||||
$connector = AlfaBank::getFacadeRoot();
|
||||
$connector->withLogging(Log::channel('daily')->getLogger());
|
||||
```
|
||||
|
||||
### Обработка Webhooks (Laravel)
|
||||
```php
|
||||
// routes/api.php
|
||||
Route::alfaWebhooks('/webhooks/alfa');
|
||||
|
||||
// Listener
|
||||
public function handle(AlfaWebhookReceived $event) {
|
||||
$data = $event->webhook->data;
|
||||
// $data будет объектом TransactionWebhookData или SbpPaymentWebhookData
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT. Автор: Anatoliy Poloz.
|
||||
100
examples/08_nominal_accounts_full_lifecycle.php
Normal file
100
examples/08_nominal_accounts_full_lifecycle.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\AddressData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryRequestData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\PayoffRequestData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\PayoutRequisiteData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\RecipientData;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\CreateBeneficiaryRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\ScoreBeneficiaryRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Payouts\CreatePayoffRequest;
|
||||
use Apoloz\AlfaBank\Utils\CmsSigner;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
/**
|
||||
* ПОЛНЫЙ ЖИЗНЕННЫЙ ЦИКЛ НОМИНАЛЬНОГО СЧЕТА
|
||||
* Сценарий: Регистрация контрагента -> Верификация -> Выплата
|
||||
*/
|
||||
|
||||
// 1. Инициализация SDK
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: 'transport.crt',
|
||||
privateKeyPath: 'transport.key'
|
||||
);
|
||||
|
||||
// Данные для подписи (в реальном приложении загружаются из защищенного хранилища)
|
||||
$signCert = file_get_contents('signing.crt');
|
||||
$signKey = file_get_contents('signing.key');
|
||||
|
||||
try {
|
||||
$baas->login('client_id', 'client_secret');
|
||||
|
||||
// --- ЭТАП 1: Создание Бенефициара (Контрагента) ---
|
||||
echo "1. Регистрация бенефициара...\n";
|
||||
|
||||
$beneficiaryData = new CreateBeneficiaryRequestData(
|
||||
type: 'FL_RESIDENT',
|
||||
countryOfTaxResidence: 'RU',
|
||||
addresses: AddressData::collect([
|
||||
['type' => 'REGISTRATION_ADDRESS', 'address' => 'г. Москва, ул. Ленина 1', 'country' => 'RU']
|
||||
], DataCollection::class),
|
||||
digestSignatures: DigestSignatureData::collect([
|
||||
[
|
||||
'base64Encoded' => CmsSigner::sign('Data to sign', $signCert, $signKey),
|
||||
'signatureType' => 'cms'
|
||||
]
|
||||
], DataCollection::class),
|
||||
name: 'Иванов Иван Иванович',
|
||||
inn: '771234567890'
|
||||
);
|
||||
|
||||
$response = $baas->send(new CreateBeneficiaryRequest($beneficiaryData));
|
||||
$beneficiaryId = $response->dto()->requestId; // В некоторых версиях API возвращается UUID
|
||||
echo "Бенефициар создан. ID запроса: $beneficiaryId\n";
|
||||
|
||||
|
||||
// --- ЭТАП 2: Верификация (Скоринг) ---
|
||||
// После создания банк должен проверить бенефициара
|
||||
echo "2. Запуск верификации (скоринга)...";
|
||||
$baas->send(new ScoreBeneficiaryRequest($beneficiaryId));
|
||||
echo "Скоринг инициирован.\n";
|
||||
|
||||
|
||||
// --- ЭТАП 3: Выплата (Payoff) ---
|
||||
// Когда бенефициар проверен, можно делать выплату
|
||||
echo "3. Создание выплаты...";
|
||||
|
||||
$payoffData = new PayoffRequestData(
|
||||
type: 'PAYMENT_ORDER',
|
||||
accountNumber: '40702810...', // Ваш номинальный счет
|
||||
recipient: new RecipientData(
|
||||
name: 'Иванов Иван Иванович',
|
||||
inn: '771234567890'
|
||||
),
|
||||
payoutRequisite: new PayoutRequisiteData(
|
||||
type: 'ACCOUNT',
|
||||
accountNumber: '40817810...', // Личный счет Иванова
|
||||
bik: '044525593'
|
||||
),
|
||||
amount: '1000.50',
|
||||
purpose: 'Выплата по договору комиссии',
|
||||
digestSignatures: DigestSignatureData::collect([
|
||||
[
|
||||
'base64Encoded' => '...', // Здесь должна быть подпись тела выплаты
|
||||
'signatureType' => 'cms'
|
||||
]
|
||||
], DataCollection::class)
|
||||
);
|
||||
|
||||
$payoffResponse = $baas->send(new CreatePayoffRequest($payoffData));
|
||||
echo "Выплата создана. ID: " . $payoffResponse->dto()->payoffId . "\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "Ошибка: " . $e->getMessage() . "\n";
|
||||
}
|
||||
Reference in New Issue
Block a user