diff --git a/README.md b/README.md index 1fd541d..2ac499d 100644 --- a/README.md +++ b/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). +[![Tests](https://github.com/apoloz/alfa-bank-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/apoloz/alfa-bank-sdk/actions) +[![Static Analysis](https://img.shields.io/badge/phpstan-level%205-brightgreen.svg)](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. +## Настройка и Безопасность -```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 +Альфа-Банк требует **два набора ключей**: +1. **Транспортные (mTLS):** Для установки защищенного соединения. +2. **Прикладные (Signing):** Для цифровой подписи реестров и платежей. - 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 -``` +### Быстрый старт (Laravel) -## 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. \ No newline at end of file diff --git a/examples/08_nominal_accounts_full_lifecycle.php b/examples/08_nominal_accounts_full_lifecycle.php new file mode 100644 index 0000000..9fd751e --- /dev/null +++ b/examples/08_nominal_accounts_full_lifecycle.php @@ -0,0 +1,100 @@ + Верификация -> Выплата + */ + +// 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"; +}