forked from apoloz/alfa-bank-sdk
Add H2H authentication service and new example flows
- Implement H2H authentication service for secure client authentication - Add new examples for authorization code flow and H2H auth service - Include example for generating client secret - Update core connector and token request functionality - Enhance README with new features Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
110
README.md
110
README.md
@@ -14,9 +14,46 @@
|
||||
composer require apoloz/alfa-bank-sdk
|
||||
```
|
||||
|
||||
## Подготовка сертификатов (mTLS)
|
||||
|
||||
Взаимодействие с API Альфа-Банка требует двухстороннего TLS-соединения. Банк не выдает закрытый ключ, вы должны сгенерировать его самостоятельно.
|
||||
|
||||
**1. Генерация закрытого ключа и запроса (CSR):**
|
||||
Используйте OpenSSL для создания ключа и файла запроса. Создайте файл `openssl.cnf`:
|
||||
|
||||
```ini
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
prompt = no
|
||||
[req_distinguished_name]
|
||||
C = RU
|
||||
L = Moscow
|
||||
O = Название Вашей Компании
|
||||
CN = ФИО Ответственного
|
||||
emailAddress = your_email@company.com
|
||||
1.2.643.100.4 = 1234567890 (Ваш ИНН)
|
||||
```
|
||||
|
||||
Выполните команды в терминале:
|
||||
|
||||
```bash
|
||||
# Генерация закрытого ключа (храните его в секрете!)
|
||||
openssl genrsa -out private.key 2048
|
||||
|
||||
# Генерация запроса на сертификат
|
||||
openssl req -new -key private.key -out request.csr -config openssl.cnf -utf8
|
||||
```
|
||||
|
||||
**2. Отправка в Банк:**
|
||||
Отправьте файл `request.csr` на почту `alfa_api@alfabank.ru`. В ответ банк пришлет публичный сертификат (`client.cer` или `client.crt`).
|
||||
|
||||
**3. Использование:**
|
||||
Поместите полученный сертификат и ваш закрытый ключ в надежное место и укажите пути к ним в `.env` (переменные `ALFA_BAAS_CERT_PATH` и `ALFA_BAAS_KEY_PATH`).
|
||||
|
||||
## Конфигурация (Laravel)
|
||||
|
||||
Опубликуйте файл конфигурации:
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag=alfa-bank-config
|
||||
```
|
||||
@@ -86,16 +123,19 @@ 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' => [
|
||||
@@ -110,15 +150,18 @@ 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;
|
||||
|
||||
@@ -128,11 +171,59 @@ $signature = CmsSigner::sign($payload, $privateKey, $certificate);
|
||||
// $signature помещается в массив digestSignatures
|
||||
```
|
||||
|
||||
### 3. Авторизация Authorization Code Flow (H2H)
|
||||
|
||||
Для упрощения работы с OAuth2 (H2H) в SDK добавлен специальный helper-сервис `H2HAuthService`.
|
||||
|
||||
**1. Инициализация сервиса:**
|
||||
|
||||
```php
|
||||
$auth = AlfaBank::auth(
|
||||
clientId: '...',
|
||||
clientSecret: '...',
|
||||
redirectUri: 'https://...',
|
||||
certificatePath: '...',
|
||||
privateKeyPath: '...'
|
||||
);
|
||||
```
|
||||
|
||||
**2. Получение ссылки на вход:**
|
||||
|
||||
```php
|
||||
$url = $auth->getAuthUrl(['beneficiaries']);
|
||||
// Перенаправьте пользователя на $url
|
||||
```
|
||||
|
||||
**3. Обмен кода на токен:**
|
||||
|
||||
```php
|
||||
$tokenData = $auth->exchangeCode($code);
|
||||
// Сохраните $tokenData->refresh_token в БД
|
||||
```
|
||||
|
||||
**4. Обновление токена:**
|
||||
|
||||
```php
|
||||
$tokenData = $auth->refreshToken($savedRefreshToken);
|
||||
```
|
||||
|
||||
Полный пример: [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));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Архитектура и Потоки данных
|
||||
|
||||
### Гибридный поток: СБП -> Номинальный счет
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Покупатель
|
||||
@@ -158,6 +249,7 @@ sequenceDiagram
|
||||
## Примеры использования
|
||||
|
||||
### 1. СБП: Прием платежа на номинальный счет
|
||||
|
||||
```php
|
||||
use Apoloz\AlfaBank\Laravel\Facades\Sbp;
|
||||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||||
@@ -175,6 +267,7 @@ echo "QR URL: " . $response->dto()->payload;
|
||||
```
|
||||
|
||||
### 2. Идентификация платежа (BaaS)
|
||||
|
||||
Когда платеж пришел на номинальный счет, его нужно привязать к бенефициару:
|
||||
|
||||
```php
|
||||
@@ -195,9 +288,11 @@ AlfaBank::send(new IdentifyTransactionRequest($data));
|
||||
```
|
||||
|
||||
### 3. Работа с номинальными счетами (Бенефициары)
|
||||
|
||||
... (existing content) ...
|
||||
|
||||
### 4. Самозанятые (Регистрация доходов)
|
||||
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\SelfEmployed\RegisterIncomeRequest;
|
||||
use Apoloz\AlfaBank\Data\SelfEmployed\CreateIncomeRequestData;
|
||||
@@ -213,7 +308,9 @@ $response = AlfaBank::send(new RegisterIncomeRequest($data));
|
||||
```
|
||||
|
||||
### 5. Файловый обмен (KYC)
|
||||
|
||||
Загрузка сканов документов бенефициаров через pre-signed ссылки:
|
||||
|
||||
```php
|
||||
use Apoloz\AlfaBank\Requests\Files\UploadFileRequest;
|
||||
|
||||
@@ -228,7 +325,9 @@ AlfaBank::send(new UploadFileRequest($uploadUrl, $file));
|
||||
## Продвинутые возможности
|
||||
|
||||
### Управление возвратами СБП
|
||||
|
||||
Просмотр всей истории попыток возврата по транзакции:
|
||||
|
||||
```php
|
||||
$response = Sbp::send(new GetQrcReversalHistoryRequest($payrrn, $termNo));
|
||||
foreach ($response->dto()->refunds as $refund) {
|
||||
@@ -237,6 +336,7 @@ foreach ($response->dto()->refunds as $refund) {
|
||||
```
|
||||
|
||||
### Webhook Handling (Laravel)
|
||||
|
||||
```php
|
||||
// Обработка СБП уведомлений в Listener
|
||||
public function handle(AlfaWebhookReceived $event) {
|
||||
|
||||
@@ -16,10 +16,26 @@ $baas = AlfaBank::baas(
|
||||
|
||||
try {
|
||||
echo "Authenticating...\n";
|
||||
$baas->login(
|
||||
|
||||
// METHOD A: Standard Client Credentials (for simple services)
|
||||
// $baas->login(
|
||||
// clientId: 'your-client-id',
|
||||
// clientSecret: 'your-client-secret'
|
||||
// );
|
||||
|
||||
// METHOD B: Authorization Code Flow (H2H) (for Nominal Accounts)
|
||||
// See examples/13_h2h_auth_service.php for the full setup
|
||||
$auth = \Apoloz\AlfaBank\AlfaBank::auth(
|
||||
clientId: 'your-client-id',
|
||||
clientSecret: 'your-client-secret'
|
||||
clientSecret: 'your-client-secret',
|
||||
redirectUri: 'https://...',
|
||||
certificatePath: __DIR__ . '/certs/client.cer',
|
||||
privateKeyPath: __DIR__ . '/certs/private.key',
|
||||
environment: Environment::SANDBOX
|
||||
);
|
||||
// Assuming you have a saved refresh token:
|
||||
// $auth->refreshToken('saved_refresh_token');
|
||||
|
||||
echo "Authentication Successful!\n";
|
||||
|
||||
// 2. Create a Beneficiary (Counterparty)
|
||||
|
||||
@@ -43,7 +43,14 @@ $payload = new CreateBeneficiaryRequestData(
|
||||
);
|
||||
|
||||
// 2. Login and send request
|
||||
$baas->login('client-id', 'client-secret');
|
||||
// For Nominal Accounts, use H2H Auth (Refresh Token)
|
||||
// See examples/13_h2h_auth_service.php
|
||||
$auth = AlfaBank::auth(
|
||||
'client-id', 'client-secret', 'https://callback',
|
||||
'path/to/cert.pem', 'path/to/key.pem', Environment::SANDBOX
|
||||
);
|
||||
$auth->refreshToken('saved-refresh-token'); // Authenticates the connector
|
||||
|
||||
$response = $baas->send(new CreateBeneficiaryRequest($payload));
|
||||
|
||||
if ($response->successful()) {
|
||||
|
||||
@@ -33,7 +33,12 @@ $signCert = file_get_contents('signing.crt');
|
||||
$signKey = file_get_contents('signing.key');
|
||||
|
||||
try {
|
||||
$baas->login('client_id', 'client_secret');
|
||||
// Auth: Nominal Accounts require H2H Flow
|
||||
$auth = AlfaBank::auth(
|
||||
'client_id', 'client_secret', 'https://callback',
|
||||
'transport.crt', 'transport.key'
|
||||
);
|
||||
$auth->refreshToken('saved_refresh_token_from_db');
|
||||
|
||||
// --- ЭТАП 1: Создание Бенефициара (Контрагента) ---
|
||||
echo "1. Регистрация бенефициара...\n";
|
||||
|
||||
80
examples/12_auth_code_flow.php
Normal file
80
examples/12_auth_code_flow.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\GetBeneficiariesRequest;
|
||||
|
||||
/**
|
||||
* Authorization Code Flow (H2H) Example
|
||||
*
|
||||
* Сценарий: Обмен полученного в браузере кода на токены и дальнейшая работа через refresh_token.
|
||||
*/
|
||||
|
||||
// 1. Конфигурация
|
||||
$config = [
|
||||
'cert' => __DIR__ . '/certs/client.cer',
|
||||
'key' => __DIR__ . '/certs/private.key',
|
||||
'client_id' => getenv('ALFA_BAAS_CLIENT_ID'),
|
||||
'client_secret' => getenv('ALFA_BAAS_CLIENT_SECRET'),
|
||||
'redirect_uri' => 'https://your-app.com/callback', // Тот же, что указывали при получении кода
|
||||
'account_number' => '40702810...'
|
||||
];
|
||||
|
||||
// 2. Инициализация коннектора
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: $config['cert'],
|
||||
privateKeyPath: $config['key'],
|
||||
environment: Environment::SANDBOX
|
||||
);
|
||||
// ВАЖНО: Мы пока НЕ вызываем login()!
|
||||
|
||||
try {
|
||||
// --- СЦЕНАРИЙ А: Первый вход (у нас есть только код из браузера) ---
|
||||
// Ссылка для получения кода: https://baas.alfabank.ru/oidc/authorize?response_type=code&client_id=...
|
||||
|
||||
$authCode = 'code_from_browser_url'; // <-- Вставьте сюда код
|
||||
|
||||
if ($authCode) {
|
||||
echo "Обмениваем код на токены...\n";
|
||||
$tokenData = $baas->authWithCode($authCode, $config['redirect_uri']);
|
||||
|
||||
echo "Access Token получен!\n";
|
||||
echo "Refresh Token: " . $tokenData->refresh_token . "\n";
|
||||
echo "(Сохраните refresh_token в базу данных)\n\n";
|
||||
}
|
||||
|
||||
// --- СЦЕНАРИЙ Б: Повторный вход (у нас есть сохраненный refresh_token) ---
|
||||
$savedRefreshToken = 'your_saved_refresh_token_from_db';
|
||||
|
||||
if ($savedRefreshToken) {
|
||||
echo "Обновляем сессию по Refresh Token...\n";
|
||||
$tokenData = $baas->refreshToken($savedRefreshToken);
|
||||
|
||||
echo "Сессия обновлена. Новый Access Token действует " . $tokenData->expires_in . " сек.\n";
|
||||
if ($tokenData->refresh_token) {
|
||||
echo "Пришел новый Refresh Token (сохраните его): " . $tokenData->refresh_token . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Выполняем защищенный запрос (например, список бенефициаров)
|
||||
// Коннектор уже авторизован (внутри updateAccessToken/authWithCode вызывается authenticate())
|
||||
|
||||
echo "Запрашиваем список...\n";
|
||||
$response = $baas->send(new GetBeneficiariesRequest(
|
||||
accountNumber: $config['account_number'],
|
||||
type: 'FL_RESIDENT'
|
||||
));
|
||||
|
||||
echo "Успешно! Найдено: " . count($response->dto()->beneficiaries) . "\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "Ошибка: " . $e->getMessage() . "\n";
|
||||
if (method_exists($e, 'getResponse')) {
|
||||
echo "Body: " . $e->getResponse()->body() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
79
examples/13_h2h_auth_service.php
Normal file
79
examples/13_h2h_auth_service.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
|
||||
/**
|
||||
* Пример использования H2HAuthService для удобной авторизации
|
||||
*/
|
||||
|
||||
// 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';
|
||||
|
||||
// 2. Инициализация сервиса авторизации
|
||||
$authService = AlfaBank::auth(
|
||||
$clientId,
|
||||
$clientSecret,
|
||||
$redirectUri,
|
||||
$cert,
|
||||
$key,
|
||||
Environment::SANDBOX
|
||||
);
|
||||
|
||||
// --- СЦЕНАРИЙ 1: Генерация ссылки на вход ---
|
||||
// Выведите эту ссылку в админке или логах
|
||||
$link = $authService->getAuthUrl(scopes: ['beneficiaries', 'payment']);
|
||||
echo "1. Перейдите по ссылке для авторизации:\n$link\n\n";
|
||||
|
||||
|
||||
// --- СЦЕНАРИЙ 2: Обмен кода на токен (в callback-скрипте) ---
|
||||
// $code приходит в GET-параметре: /callback?code=...
|
||||
$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";
|
||||
|
||||
// TODO: Сохраните refresh_token в базу данных!
|
||||
// DB::table('settings')->updateOrInsert(['key' => 'alfa_refresh_token'], ['value' => $tokenData->refresh_token]);
|
||||
|
||||
} catch (
|
||||
Exception $e) {
|
||||
echo "Ошибка обмена кода: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// --- СЦЕНАРИЙ 3: Регулярное обновление токена (Cron / Middleware) ---
|
||||
$savedRefreshToken = '...saved_refresh_token...';
|
||||
|
||||
if ($savedRefreshToken !== '...saved_refresh_token...') {
|
||||
try {
|
||||
echo "3. Обновление токена...\n";
|
||||
$newTokenData = $authService->refreshToken($savedRefreshToken);
|
||||
|
||||
echo "Токен обновлен!\n";
|
||||
|
||||
// Если пришел новый refresh_token — обновляем его
|
||||
if ($newTokenData->refresh_token) {
|
||||
// DB::update(...)
|
||||
echo "Новый Refresh Token получен.\n";
|
||||
}
|
||||
} catch (
|
||||
Exception $e) {
|
||||
echo "Ошибка обновления токена: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
58
examples/14_generate_client_secret.php
Normal file
58
examples/14_generate_client_secret.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Requests\Auth\RefreshClientSecretRequest;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
|
||||
/**
|
||||
* UTILITY SCRIPT: Generate a Client Secret using your TLS certificates.
|
||||
*
|
||||
* 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).
|
||||
* 2. You have the TLS certificates (.pem/.key) authorized by the bank.
|
||||
*/
|
||||
|
||||
// 1. Configuration
|
||||
$certPath = __DIR__ . '/certs/transport.crt'; // Your mTLS certificate
|
||||
$keyPath = __DIR__ . '/certs/transport.key'; // Your mTLS private key
|
||||
$clientId = 'YOUR_CLIENT_ID_UUID'; // E.g. '0cee0683-85ae-...'
|
||||
|
||||
// 2. Initialize Connector (No Login required yet)
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: $certPath,
|
||||
privateKeyPath: $keyPath,
|
||||
environment: Environment::SANDBOX // Change to PRODUCTION for real usage
|
||||
);
|
||||
|
||||
try {
|
||||
echo "Requesting new Client Secret for Client ID: {$clientId}...\n";
|
||||
|
||||
// 3. Send Request
|
||||
$request = new RefreshClientSecretRequest($clientId);
|
||||
$response = $baas->send($request);
|
||||
|
||||
// 4. Output Result
|
||||
$data = $response->dto(); // Returns ClientSecretData
|
||||
|
||||
echo "\n-------------------------------------------------------\n";
|
||||
echo "SUCCESS! New Client Secret Generated:\n";
|
||||
echo "-------------------------------------------------------\n";
|
||||
echo "Client ID: " . $data->clientId . "\n";
|
||||
echo "Client Secret: " . $data->clientSecret . "\n";
|
||||
echo "-------------------------------------------------------\n";
|
||||
echo "IMPORTANT: Save this to your .env file immediately.\n";
|
||||
echo "It will not be shown again.\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "\nERROR: Could not generate secret.\n";
|
||||
echo "Message: " . $e->getMessage() . "\n";
|
||||
if (method_exists($e, 'getResponse')) {
|
||||
echo "Response: " . $e->getResponse()->body() . "\n";
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,36 @@ 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.
|
||||
|
||||
@@ -79,6 +79,55 @@ 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;
|
||||
}
|
||||
|
||||
public function login(?string $clientId = null, ?string $clientSecret = null): static
|
||||
{
|
||||
// Allow overriding config via method arguments
|
||||
|
||||
@@ -42,6 +42,7 @@ class GetTokenRequest extends Request implements HasBody
|
||||
protected string $clientSecret,
|
||||
protected ?string $code = null,
|
||||
protected ?string $redirectUri = null,
|
||||
protected ?string $refreshToken = null,
|
||||
protected string $grantType = 'authorization_code'
|
||||
) {}
|
||||
// </SECTION>
|
||||
@@ -64,11 +65,15 @@ class GetTokenRequest extends Request implements HasBody
|
||||
'client_secret' => $this->clientSecret,
|
||||
];
|
||||
|
||||
if($this->grantType === 'authorization_code') {
|
||||
if ($this->grantType === 'authorization_code') {
|
||||
$body['code'] = $this->code;
|
||||
$body['redirect_uri'] = $this->redirectUri;
|
||||
}
|
||||
|
||||
if ($this->grantType === 'refresh_token') {
|
||||
$body['refresh_token'] = $this->refreshToken;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
61
src/Services/H2HAuthService.php
Normal file
61
src/Services/H2HAuthService.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Services;
|
||||
|
||||
use Apoloz\AlfaBank\Core\AlfaBankConnector;
|
||||
use Apoloz\AlfaBank\Data\Auth\TokenData;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
|
||||
class H2HAuthService
|
||||
{
|
||||
public function __construct(
|
||||
protected AlfaBankConnector $connector,
|
||||
protected string $clientId,
|
||||
protected string $redirectUri,
|
||||
protected Environment $environment = Environment::SANDBOX
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Step 1: Generate the URL for the user to authorize via Alfa ID.
|
||||
*
|
||||
* @param array<string> $scopes List of scopes (e.g. ['beneficiaries', 'payment'])
|
||||
* @param string|null $state Optional state parameter for CSRF protection
|
||||
*/
|
||||
public function getAuthUrl(array $scopes = ['beneficiaries'], ?string $state = null): string
|
||||
{
|
||||
$baseUrl = $this->environment === Environment::PRODUCTION
|
||||
? 'https://id.alfabank.ru/oidc/authorize'
|
||||
: 'https://id-sandbox.alfabank.ru/oidc/authorize';
|
||||
|
||||
$params = [
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->clientId,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'scope' => implode(' ', $scopes),
|
||||
];
|
||||
|
||||
if ($state) {
|
||||
$params['state'] = $state;
|
||||
}
|
||||
|
||||
return $baseUrl . '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Exchange the received code for Access & Refresh Tokens.
|
||||
*/
|
||||
public function exchangeCode(string $code): TokenData
|
||||
{
|
||||
return $this->connector->authWithCode($code, $this->redirectUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Refresh the token using a stored refresh token.
|
||||
*/
|
||||
public function refreshToken(string $refreshToken): TokenData
|
||||
{
|
||||
return $this->connector->refreshToken($refreshToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user