From bb5f91d399fb67ebbc962c5767b82a8efefee727 Mon Sep 17 00:00:00 2001 From: Anatoliy Poloz Date: Sat, 24 Jan 2026 17:18:59 +0300 Subject: [PATCH] Update documentation and examples, add new nominal accounts features and authentication methods Co-authored-by: Qwen-Coder --- README.md | 72 ++++++--------- examples/01_baas_flow.php | 16 ++-- examples/04_nominal_accounts.php | 6 +- .../08_nominal_accounts_full_lifecycle.php | 4 + examples/12_auth_code_flow.php | 15 +++- examples/13_h2h_auth_service.php | 57 ++++++------ examples/14_generate_client_secret.php | 4 +- examples/15_nominal_accounts_list.php | 70 +++++++++++++++ examples/16_automated_startup.php | 89 +++++++++++++++++++ src/AlfaBank.php | 32 ++----- src/Core/AlfaBankConnector.php | 77 +++++----------- src/Core/AlfaConfig.php | 2 + src/Enums/Environment.php | 3 + 13 files changed, 290 insertions(+), 157 deletions(-) create mode 100644 examples/15_nominal_accounts_list.php create mode 100644 examples/16_automated_startup.php diff --git a/README.md b/README.md index f12ad9f..900453e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Alfa Bank SDK (Unofficial) +[![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/) + Профессиональная PHP-библиотека для интеграции с API Альфа-Банка (BaaS и СБП). Построена на базе [Saloon v3](https://docs.saloon.dev) и [Spatie Laravel Data](https://spatie.be/docs/laravel-data). ## Требования @@ -20,7 +23,6 @@ composer require apoloz/alfa-bank-sdk **1. Генерация закрытого ключа и запроса (CSR):** Используйте OpenSSL для создания ключа и файла запроса. Создайте файл `openssl.cnf`: - ```ini [req] distinguished_name = req_distinguished_name @@ -35,7 +37,6 @@ emailAddress = your_email@company.com ``` Выполните команды в терминале: - ```bash # Генерация закрытого ключа (храните его в секрете!) openssl genrsa -out private.key 2048 @@ -53,7 +54,6 @@ openssl req -new -key private.key -out request.csr -config openssl.cnf -utf8 ## Конфигурация (Laravel) Опубликуйте файл конфигурации: - ```bash php artisan vendor:publish --tag=alfa-bank-config ``` @@ -123,19 +123,16 @@ SDK поддерживает две разные модели безопасно ### 1. Подключение к СБП (Pay-in) **Шаги для получения доступа:** - 1. Генерируете пару ключей: `openssl req -x509 -newkey rsa:2048 -keyout client.key -out client.crt -nodes -days 3650`. 2. Отправляете `client.crt` менеджеру банка (acquiring@alfabank.ru). 3. Получаете от банка `alias` (key-name) и `TermNo`. **Поток данных и подпись:** - -- **Transport**: Обязательный **MTLS**. Вы должны передать `client.crt` и `client.key` в конфигурацию Guzzle/Saloon. -- **Payload**: Тело запроса канонизируется по алгоритму **JCS**. -- **Signature**: SHA256withRSA подпись JCS-строки помещается в заголовок `Authorization`. +* **Transport**: Обязательный **MTLS**. Вы должны передать `client.crt` и `client.key` в конфигурацию Guzzle/Saloon. +* **Payload**: Тело запроса канонизируется по алгоритму **JCS**. +* **Signature**: SHA256withRSA подпись JCS-строки помещается в заголовок `Authorization`. **SDK делает это автоматически**: - ```php // В config/alfa-bank.php 'sbp' => [ @@ -150,18 +147,15 @@ SDK поддерживает две разные модели безопасно ### 2. Подключение к BaaS (Номинальные счета) **Шаги для получения доступа:** - 1. Регистрация в личном кабинете партнера. 2. Получение `client_id` и `client_secret` для OAuth2. 3. Регистрация публичного ключа для проверки подписей выплат. **Поток данных и подпись:** - -- **Auth**: Стандартный OAuth2 Bearer Token. -- **Payload Signature**: Для критических операций (выплаты, идентификация) требуется `digestSignatures`. Это массив CMS-подписей (PKCS7) в формате Base64, которыми подписываются финансовые поля. +* **Auth**: Стандартный OAuth2 Bearer Token. +* **Payload Signature**: Для критических операций (выплаты, идентификация) требуется `digestSignatures`. Это массив CMS-подписей (PKCS7) в формате Base64, которыми подписываются финансовые поля. **Пример формирования CMS в SDK:** - ```php use Apoloz\AlfaBank\Utils\CmsSigner; @@ -173,57 +167,54 @@ $signature = CmsSigner::sign($payload, $privateKey, $certificate); ### 3. Авторизация Authorization Code Flow (H2H) -Для упрощения работы с OAuth2 (H2H) в SDK добавлен специальный helper-сервис `H2HAuthService`. - -**1. Инициализация сервиса:** +SDK использует нативный функционал Saloon для OAuth2. +**1. Инициализация:** ```php -$auth = AlfaBank::auth( - clientId: '...', - clientSecret: '...', - redirectUri: 'https://...', - certificatePath: '...', - privateKeyPath: '...' +$baas = AlfaBank::baas( + clientId: '...', clientSecret: '...', redirectUri: '...', + certificatePath: '...', privateKeyPath: '...' ); ``` **2. Получение ссылки на вход:** - ```php -$url = $auth->getAuthUrl(['beneficiaries']); +$url = $baas->getAuthorizationUrl(['beneficiaries']); // Перенаправьте пользователя на $url ``` **3. Обмен кода на токен:** - ```php -$tokenData = $auth->exchangeCode($code); -// Сохраните $tokenData->refresh_token в БД +$authenticator = $baas->getAccessToken($code); +$baas->authenticate($authenticator); // Применить токен +// Сохраните $authenticator->getRefreshToken() в БД ``` **4. Обновление токена:** - ```php -$tokenData = $auth->refreshToken($savedRefreshToken); +$newAuth = $baas->refreshAccessToken($savedRefreshToken); +$baas->authenticate($newAuth); ``` Полный пример: [examples/13_h2h_auth_service.php](examples/13_h2h_auth_service.php) ### 4. Генерация Client Secret (Утилита) - Если банк выдал только сертификаты, вы можете сгенерировать `client_secret` самостоятельно: - ```php // См. examples/14_generate_client_secret.php $baas->send(new RefreshClientSecretRequest($clientId)); ``` +> **Важно для продакшена:** +> Запрос нового секрета **инвалидирует** старый. Если у вас запущено несколько экземпляров приложения (или worker-ов), не запрашивайте секрет при каждом старте. +> **Решение:** Получите секрет один раз, сохраните в общий кеш (Redis/DB) с TTL (например, 29 дней) и запрашивайте новый только при истечении срока или отсутствии в кеше. +> Полный пример автоматизации: [examples/16_automated_startup.php](examples/16_automated_startup.php) + --- ## Архитектура и Потоки данных ### Гибридный поток: СБП -> Номинальный счет - ```mermaid sequenceDiagram participant Client as Покупатель @@ -238,7 +229,7 @@ sequenceDiagram Note over API, BaaS: Webhook with Signature API->>BaaS: 4. Webhook (Входящий платеж) - + Note over BaaS, API: OAuth2 + CMS Signature BaaS->>API: 5. IdentifyTransactionRequest(beneficiaryId) API-->>BaaS: 6. 200 OK (Зачислено) @@ -249,7 +240,6 @@ sequenceDiagram ## Примеры использования ### 1. СБП: Прием платежа на номинальный счет - ```php use Apoloz\AlfaBank\Laravel\Facades\Sbp; use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest; @@ -267,7 +257,6 @@ echo "QR URL: " . $response->dto()->payload; ``` ### 2. Идентификация платежа (BaaS) - Когда платеж пришел на номинальный счет, его нужно привязать к бенефициару: ```php @@ -288,11 +277,9 @@ AlfaBank::send(new IdentifyTransactionRequest($data)); ``` ### 3. Работа с номинальными счетами (Бенефициары) - ... (existing content) ... ### 4. Самозанятые (Регистрация доходов) - ```php use Apoloz\AlfaBank\Requests\SelfEmployed\RegisterIncomeRequest; use Apoloz\AlfaBank\Data\SelfEmployed\CreateIncomeRequestData; @@ -308,9 +295,7 @@ $response = AlfaBank::send(new RegisterIncomeRequest($data)); ``` ### 5. Файловый обмен (KYC) - Загрузка сканов документов бенефициаров через pre-signed ссылки: - ```php use Apoloz\AlfaBank\Requests\Files\UploadFileRequest; @@ -325,9 +310,7 @@ AlfaBank::send(new UploadFileRequest($uploadUrl, $file)); ## Продвинутые возможности ### Управление возвратами СБП - Просмотр всей истории попыток возврата по транзакции: - ```php $response = Sbp::send(new GetQrcReversalHistoryRequest($payrrn, $termNo)); foreach ($response->dto()->refunds as $refund) { @@ -336,7 +319,6 @@ foreach ($response->dto()->refunds as $refund) { ``` ### Webhook Handling (Laravel) - ```php // Обработка СБП уведомлений в Listener public function handle(AlfaWebhookReceived $event) { @@ -346,3 +328,7 @@ public function handle(AlfaWebhookReceived $event) { } } ``` + +## Лицензия + +MIT. Автор: Anatoliy Poloz. diff --git a/examples/01_baas_flow.php b/examples/01_baas_flow.php index 278856d..76e1214 100644 --- a/examples/01_baas_flow.php +++ b/examples/01_baas_flow.php @@ -6,24 +6,30 @@ use Apoloz\AlfaBank\AlfaBank; use Apoloz\AlfaBank\Enums\Environment; // 1. Initialize -// Ensure you have valid certificates for the Sandbox +// CERTIFICATES: +// - Sandbox: Provided by bank via email or portal (often a .pfx or zip). +// - Production: Generated by YOU (openssl), public part sent to bank for activation. $baas = AlfaBank::baas( certificatePath: __DIR__ . '/certs/client.cer', privateKeyPath: __DIR__ . '/certs/private.key', environment: Environment::SANDBOX, - caBundlePath: __DIR__ . '/certs/russian_trusted_sub_ca.crt' // Mincifry + caBundlePath: __DIR__ . '/certs/russian_trusted_sub_ca.crt' // Optional: for trusted chain validation ); try { echo "Authenticating...\n"; + // AUTHENTICATION: + // client_id: From Developer Portal (App Settings) + // client_secret: From Developer Portal (App Settings -> Key Icon) OR generated via SDK (examples/14_generate_client_secret.php) + // METHOD A: Standard Client Credentials (for simple services) // $baas->login( - // clientId: 'your-client-id', - // clientSecret: 'your-client-secret' + // clientId: 'your-client-id-uuid', + // clientSecret: 'your-client-secret-string' // ); - // METHOD B: Authorization Code Flow (H2H) (for Nominal Accounts) + // METHOD B: Authorization Code Flow (H2H) (REQUIRED for Nominal Accounts) // See examples/13_h2h_auth_service.php for the full setup $auth = \Apoloz\AlfaBank\AlfaBank::auth( clientId: 'your-client-id', diff --git a/examples/04_nominal_accounts.php b/examples/04_nominal_accounts.php index dcf9f3d..da187ae 100644 --- a/examples/04_nominal_accounts.php +++ b/examples/04_nominal_accounts.php @@ -44,7 +44,11 @@ $payload = new CreateBeneficiaryRequestData( // 2. Login and send request // For Nominal Accounts, use H2H Auth (Refresh Token) -// See examples/13_h2h_auth_service.php +// See examples/13_h2h_auth_service.php for setting this up. + +// CREDENTIALS SOURCE: +// - client-id: Developer Portal -> App -> Overview +// - client-secret: Developer Portal -> App -> Keys (Click Eye Icon) OR SDK Generator $auth = AlfaBank::auth( 'client-id', 'client-secret', 'https://callback', 'path/to/cert.pem', 'path/to/key.pem', Environment::SANDBOX diff --git a/examples/08_nominal_accounts_full_lifecycle.php b/examples/08_nominal_accounts_full_lifecycle.php index b79df2c..3a61633 100644 --- a/examples/08_nominal_accounts_full_lifecycle.php +++ b/examples/08_nominal_accounts_full_lifecycle.php @@ -23,6 +23,8 @@ use Spatie\LaravelData\DataCollection; */ // 1. Инициализация SDK +// Сертификаты (Transport Certs): +// Получены от банка (Sandbox) или сгенерированы вами и активированы банком (Production). $baas = AlfaBank::baas( certificatePath: 'transport.crt', privateKeyPath: 'transport.key' @@ -34,6 +36,8 @@ $signKey = file_get_contents('signing.key'); try { // Auth: Nominal Accounts require H2H Flow + // client_id: Из Портала разработчика + // client_secret: Из Портала разработчика (глаз) или сгенерирован через SDK (examples/14_generate_client_secret.php) $auth = AlfaBank::auth( 'client_id', 'client_secret', 'https://callback', 'transport.crt', 'transport.key' diff --git a/examples/12_auth_code_flow.php b/examples/12_auth_code_flow.php index 2f5439b..2a9aee6 100644 --- a/examples/12_auth_code_flow.php +++ b/examples/12_auth_code_flow.php @@ -16,11 +16,24 @@ use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\GetBeneficiariesReque // 1. Конфигурация $config = [ + // CERTIFICATES: + // Production: You generated `private.key` and sent CSR to bank. Bank sent back `client.cer`. + // Sandbox: Bank sent you these via email/portal. 'cert' => __DIR__ . '/certs/client.cer', 'key' => __DIR__ . '/certs/private.key', + + // APP CREDENTIALS (OAUTH2): + // From: Developer Portal -> Your App -> Settings/Keys + // Note: If you don't have client_secret, generate it using `14_generate_client_secret.php` 'client_id' => getenv('ALFA_BAAS_CLIENT_ID'), 'client_secret' => getenv('ALFA_BAAS_CLIENT_SECRET'), - 'redirect_uri' => 'https://your-app.com/callback', // Тот же, что указывали при получении кода + + // REDIRECT URI: + // Must match exactly what you entered in Developer Portal for this App. + 'redirect_uri' => 'https://your-app.com/callback', + + // BUSINESS DATA: + // From your Contract or Internet Bank. Not related to Auth directly. 'account_number' => '40702810...' ]; diff --git a/examples/13_h2h_auth_service.php b/examples/13_h2h_auth_service.php index d61fc89..7fbe74d 100644 --- a/examples/13_h2h_auth_service.php +++ b/examples/13_h2h_auth_service.php @@ -12,25 +12,26 @@ use Apoloz\AlfaBank\Enums\Environment; */ // 1. Конфигурация -$clientId = getenv('ALFA_BAAS_CLIENT_ID'); -$clientSecret = getenv('ALFA_BAAS_CLIENT_SECRET'); -$redirectUri = 'https://your-domain.com/callback'; -$cert = __DIR__ . '/certs/client.cer'; -$key = __DIR__ . '/certs/private.key'; +// Все эти данные вы получаете на Портале Разработчика и в Интернет-банке. +$clientId = getenv('ALFA_BAAS_CLIENT_ID'); // ID приложения (Portal) +$clientSecret = getenv('ALFA_BAAS_CLIENT_SECRET'); // Secret Key (Portal OR Generated via SDK) +$redirectUri = 'https://your-domain.com/callback'; // Callback URL (Portal) +$cert = __DIR__ . '/certs/client.cer'; // Public Cert (From Bank) +$key = __DIR__ . '/certs/private.key'; // Private Key (Generated by You) -// 2. Инициализация сервиса авторизации -$authService = AlfaBank::auth( - $clientId, - $clientSecret, - $redirectUri, - $cert, - $key, - Environment::SANDBOX +// 2. Инициализация SDK (сразу с кредами) +$baas = AlfaBank::baas( + certificatePath: $cert, + privateKeyPath: $key, + environment: Environment::SANDBOX, + clientId: $clientId, + clientSecret: $clientSecret, + redirectUri: $redirectUri ); // --- СЦЕНАРИЙ 1: Генерация ссылки на вход --- -// Выведите эту ссылку в админке или логах -$link = $authService->getAuthUrl(scopes: ['beneficiaries', 'payment']); +// Используем нативный метод Saloon +$link = $baas->getAuthorizationUrl(scopes: ['beneficiaries', 'payment']); echo "1. Перейдите по ссылке для авторизации:\n$link\n\n"; @@ -41,17 +42,17 @@ $codeFromUrl = '...код из браузера...'; if ($codeFromUrl !== '...код из браузера...') { try { echo "2. Обмен кода на токены...\n"; - $tokenData = $authService->exchangeCode($codeFromUrl); - echo "Access Token: " . substr($tokenData->access_token, 0, 20) . "...\n"; - echo "Refresh Token: " . $tokenData->refresh_token . "\n"; - echo "Expires In: " . $tokenData->expires_in . " sec\n"; + // Saloon делает всё сам: + $authenticator = $baas->getAccessToken($codeFromUrl); + + echo "Access Token: " . substr($authenticator->getAccessToken(), 0, 20) . "...\n"; + echo "Refresh Token: " . $authenticator->getRefreshToken() . "\n"; + echo "Expires At: " . $authenticator->getExpiresAt()->format('Y-m-d H:i:s') . "\n"; // TODO: Сохраните refresh_token в базу данных! - // DB::table('settings')->updateOrInsert(['key' => 'alfa_refresh_token'], ['value' => $tokenData->refresh_token]); - } catch ( -Exception $e) { + } catch (\Exception $e) { echo "Ошибка обмена кода: " . $e->getMessage() . "\n"; } } @@ -62,17 +63,21 @@ $savedRefreshToken = '...saved_refresh_token...'; if ($savedRefreshToken !== '...saved_refresh_token...') { try { echo "3. Обновление токена...\n"; - $newTokenData = $authService->refreshToken($savedRefreshToken); + + // Обновляем токен + $newAuthenticator = $baas->refreshAccessToken($savedRefreshToken); + + // Автоматически применяем к коннектору (для текущего скрипта) + $baas->authenticate($newAuthenticator); echo "Токен обновлен!\n"; // Если пришел новый refresh_token — обновляем его - if ($newTokenData->refresh_token) { + if ($newAuthenticator->getRefreshToken()) { // DB::update(...) echo "Новый Refresh Token получен.\n"; } - } catch ( -Exception $e) { + } catch (\Exception $e) { echo "Ошибка обновления токена: " . $e->getMessage() . "\n"; } } diff --git a/examples/14_generate_client_secret.php b/examples/14_generate_client_secret.php index b8d742e..df7d6c5 100644 --- a/examples/14_generate_client_secret.php +++ b/examples/14_generate_client_secret.php @@ -14,8 +14,10 @@ use Apoloz\AlfaBank\Enums\Environment; * Run this ONCE when setting up the project or if you lose your secret. * * Requirements: - * 1. You have the `client_id` (from the bank/portal). + * 1. You have the `client_id` (Found in Developer Portal -> App Settings URL or Overview). * 2. You have the TLS certificates (.pem/.key) authorized by the bank. + * - Prod: You sent CSR to bank, they enabled integration and sent .cer back. + * - Sandbox: You downloaded them from the email/portal. */ // 1. Configuration diff --git a/examples/15_nominal_accounts_list.php b/examples/15_nominal_accounts_list.php new file mode 100644 index 0000000..de83b78 --- /dev/null +++ b/examples/15_nominal_accounts_list.php @@ -0,0 +1,70 @@ + getenv('ALFA_BAAS_CLIENT_ID'), + 'client_secret' => getenv('ALFA_BAAS_CLIENT_SECRET'), + 'redirect_uri' => 'https://your-app.com/callback', + 'cert_path' => __DIR__ . '/certs/client.cer', + 'key_path' => __DIR__ . '/certs/private.key', + + // BUSINESS DATA + 'account_num' => '40702810...', // Nominal Account Number + 'refresh_token' => 'saved_refresh_token_db' // Your stored token +]; + +try { + // 1. Initialize Connector with Auth Config + $baas = AlfaBank::baas( + certificatePath: $config['cert_path'], + privateKeyPath: $config['key_path'], + environment: Environment::SANDBOX, + clientId: $config['client_id'], + clientSecret: $config['client_secret'], + redirectUri: $config['redirect_uri'] + ); + + // 2. Refresh Token (Get valid Access Token for this session) + echo "Refreshing session...\n"; + $authenticator = $baas->refreshAccessToken($config['refresh_token']); + + // Apply authentication to the connector + $baas->authenticate($authenticator); + + // 3. Perform Business Request + echo "Requesting beneficiaries list...\n"; + $request = new GetBeneficiariesRequest( + accountNumber: $config['account_num'], + type: 'FL_RESIDENT' + ); + + $response = $baas->send($request); + + // 4. Handle Result + $list = $response->dto(); + echo "Found " . count($list->beneficiaries) . " beneficiaries.\n"; + + foreach ($list->beneficiaries as $b) { + echo "- {$b->name} (ID: {$b->beneficiaryId})\n"; + } + +} catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; +} diff --git a/examples/16_automated_startup.php b/examples/16_automated_startup.php new file mode 100644 index 0000000..9eac1e5 --- /dev/null +++ b/examples/16_automated_startup.php @@ -0,0 +1,89 @@ +send(new RefreshClientSecretRequest($clientId)); + $tempSecret = $secretResponse->dto()->clientSecret; + + echo " Secret acquired: " . substr($tempSecret, 0, 5) . "...\n"; + + // 3. AUTHENTICATE OAUTH2 + // Now we use the fresh secret to get an access token. + echo "2. Refreshing OAuth2 session...\n"; + + // Re-initialize connector with the new secret + $baasReady = AlfaBank::baas( + certificatePath: $certPath, + privateKeyPath: $keyPath, + environment: Environment::SANDBOX, + clientId: $clientId, + clientSecret: $tempSecret, + redirectUri: $redirectUri + ); + + // Refresh token using native method + $authenticator = $baasReady->refreshAccessToken($savedRefreshToken); + $baasReady->authenticate($authenticator); + + echo " Session active. Access Token expires at " . $authenticator->getExpiresAt()->format('H:i:s') . "\n"; + + // Update DB if refresh token rotated + if ($authenticator->getRefreshToken()) { + // saveNewRefreshToken($authenticator->getRefreshToken()); + echo " New Refresh Token saved.\n"; + } + + // 4. BUSINESS LOGIC + echo "3. Fetching beneficiaries...\n"; + + $response = $baasReady->send(new GetBeneficiariesRequest($accNum, 'FL_RESIDENT')); + + echo " Success! Found " . count($response->dto()->beneficiaries) . " records.\n"; + +} catch (Exception $e) { + echo "CRITICAL ERROR: " . $e->getMessage() . "\n"; + if (method_exists($e, 'getResponse')) { + echo "Body: " . $e->getResponse()->body() . "\n"; + } +} diff --git a/src/AlfaBank.php b/src/AlfaBank.php index 6cfcb78..f45116d 100644 --- a/src/AlfaBank.php +++ b/src/AlfaBank.php @@ -11,36 +11,10 @@ use Apoloz\AlfaBank\Core\AlfaConfig; use Apoloz\AlfaBank\Enums\Environment; use Apoloz\AlfaBank\Services\OpenSslCmsSigner; use Apoloz\AlfaBank\Sbp\SbpConnector; -use Apoloz\AlfaBank\Services\H2HAuthService; use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest; class AlfaBank { - /** - * Creates a helper service for H2H Authorization (Authorization Code Flow). - */ - public static function auth( - string $clientId, - string $clientSecret, - string $redirectUri, - string $certificatePath, - string $privateKeyPath, - Environment $environment = Environment::SANDBOX - ): H2HAuthService { - $config = new \Apoloz\AlfaBank\Core\AlfaConfig( - baseUrl: $environment->value, - certificatePath: $certificatePath, - privateKeyPath: $privateKeyPath, - clientId: $clientId, - clientSecret: $clientSecret - ); - - $signer = new \Apoloz\AlfaBank\Services\OpenSslCmsSigner($certificatePath, $privateKeyPath); - $connector = new \Apoloz\AlfaBank\Core\AlfaBankConnector($config, $signer); - - return new H2HAuthService($connector, $clientId, $redirectUri, $environment); - } - /** * Performs a health check on the connection. * Throws an exception if authentication or account listing fails. @@ -64,6 +38,9 @@ class AlfaBank string $privateKeyPath, Environment $environment = Environment::SANDBOX, ?string $caBundlePath = null, + ?string $clientId = null, + ?string $clientSecret = null, + ?string $redirectUri = null, ?string $signingCertificatePath = null, ?string $signingPrivateKeyPath = null ): AlfaBankConnector { @@ -72,6 +49,9 @@ class AlfaBank certificatePath: $certificatePath, privateKeyPath: $privateKeyPath, caBundlePath: $caBundlePath, + clientId: $clientId, + clientSecret: $clientSecret, + redirectUri: $redirectUri, signingCertificatePath: $signingCertificatePath, signingPrivateKeyPath: $signingPrivateKeyPath ); diff --git a/src/Core/AlfaBankConnector.php b/src/Core/AlfaBankConnector.php index 7a63b73..425c3f5 100644 --- a/src/Core/AlfaBankConnector.php +++ b/src/Core/AlfaBankConnector.php @@ -9,8 +9,9 @@ namespace Apoloz\AlfaBank\Core; use Apoloz\AlfaBank\Contracts\SignerInterface; use Apoloz\AlfaBank\Http\Middleware\RequestIdMiddleware; use Apoloz\AlfaBank\Http\Pagination\AlfaOffsetPaginator; -use Apoloz\AlfaBank\Requests\Auth\GetTokenRequest; +use Apoloz\AlfaBank\Requests\Auth\GetTokenRequest; // Will be removed use Apoloz\AlfaBank\Traits\HasAlfaErrorHandling; +use Saloon\Helpers\OAuth2\OAuthConfig; use Saloon\Http\Connector; use Saloon\Http\Request; use Saloon\PaginationPlugin\Contracts\HasPagination; @@ -18,13 +19,17 @@ use Saloon\PaginationPlugin\OffsetPaginator; use Saloon\Traits\Plugins\AlwaysThrowOnErrors; use Saloon\Traits\RequestProperties\HasTries; use Saloon\Traits\Plugins\HasTimeout; +use Saloon\Contracts\OAuth2\OAuth2Api; +use Saloon\Traits\OAuth2\AuthorizationCodeGrant; +use Apoloz\AlfaBank\Enums\Environment; -class AlfaBankConnector extends Connector implements HasPagination +class AlfaBankConnector extends Connector implements HasPagination, OAuth2Api { use HasTimeout; use HasTries; use AlwaysThrowOnErrors; use HasAlfaErrorHandling; + use AuthorizationCodeGrant; public function __construct( protected AlfaConfig $alfaConfig, @@ -39,6 +44,20 @@ class AlfaBankConnector extends Connector implements HasPagination return $this->alfaConfig->baseUrl; } + protected function resolveOauth2Config(): OAuthConfig + { + $idUrl = $this->alfaConfig->baseUrl === Environment::PRODUCTION->value + ? Environment::ID_PRODUCTION->value + : Environment::ID_SANDBOX->value; + + return OAuthConfig::make() + ->setClientId($this->alfaConfig->clientId ?? '') + ->setClientSecret($this->alfaConfig->clientSecret ?? '') + ->setRedirectUri($this->alfaConfig->redirectUri ?? '') + ->setAuthorizeEndpoint($idUrl . '/oidc/authorize') + ->setTokenEndpoint('/oidc/token'); + } + protected function defaultConfig(): array { $config = [ @@ -79,55 +98,7 @@ class AlfaBankConnector extends Connector implements HasPagination return new AlfaOffsetPaginator($this, $request); } - /** - * Exchange Authorization Code for tokens (H2H Flow). - */ - public function authWithCode(string $code, string $redirectUri): \Apoloz\AlfaBank\Data\Auth\TokenData - { - if (!$this->alfaConfig->clientId || !$this->alfaConfig->clientSecret) { - throw new \RuntimeException("Client ID/Secret not configured."); - } - - $request = new GetTokenRequest( - clientId: $this->alfaConfig->clientId, - clientSecret: $this->alfaConfig->clientSecret, - code: $code, - redirectUri: $redirectUri, - grantType: 'authorization_code' - ); - - $response = $this->send($request); - $data = $response->dto(); - - $this->authenticate(new \Saloon\Http\Auth\TokenAuthenticator($data->access_token)); - - return $data; - } - - /** - * Refresh access token using refresh token. - */ - public function refreshToken(string $refreshToken): \Apoloz\AlfaBank\Data\Auth\TokenData - { - if (!$this->alfaConfig->clientId || !$this->alfaConfig->clientSecret) { - throw new \RuntimeException("Client ID/Secret not configured."); - } - - $request = new GetTokenRequest( - clientId: $this->alfaConfig->clientId, - clientSecret: $this->alfaConfig->clientSecret, - refreshToken: $refreshToken, - grantType: 'refresh_token' - ); - - $response = $this->send($request); - $data = $response->dto(); - - $this->authenticate(new \Saloon\Http\Auth\TokenAuthenticator($data->access_token)); - - return $data; - } - + // login() kept for ClientCredentials compatibility (legacy/simple flow) public function login(?string $clientId = null, ?string $clientSecret = null): static { // Allow overriding config via method arguments @@ -144,9 +115,6 @@ class AlfaBankConnector extends Connector implements HasPagination grantType: 'client_credentials' ); - // Send request (using self to reuse cert/ssl config) - // We temporarily disable auth middleware if we had any, - // but here we are raw, so it's fine. $response = $this->send($request); $data = $response->dto(); @@ -155,6 +123,7 @@ class AlfaBankConnector extends Connector implements HasPagination return $this; } // + // //
// diff --git a/src/Core/AlfaConfig.php b/src/Core/AlfaConfig.php index 239e32d..3c172c2 100644 --- a/src/Core/AlfaConfig.php +++ b/src/Core/AlfaConfig.php @@ -33,6 +33,8 @@ readonly class AlfaConfig public ?string $clientId = null, // public ?string $clientSecret = null, + // + public ?string $redirectUri = null, // public ?string $signingCertificatePath = null, // diff --git a/src/Enums/Environment.php b/src/Enums/Environment.php index be5608a..4838282 100644 --- a/src/Enums/Environment.php +++ b/src/Enums/Environment.php @@ -26,5 +26,8 @@ enum Environment: string case SBP_PRODUCTION = 'https://pay.alfabank.ru/fsCryptoProxy'; case SBP_SANDBOX = 'https://217.12.103.132:2443/fsCryptoProxy'; //
+ + case ID_PRODUCTION = 'https://id.alfabank.ru'; + case ID_SANDBOX = 'https://id-sandbox.alfabank.ru'; } //