forked from apoloz/alfa-bank-sdk
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b15d29f8e9 | ||
|
|
1723177949 | ||
| d89a78bef2 | |||
| ca3f14485a | |||
| 86f50eed6a | |||
| e83bb5e7e7 | |||
| e23e2ea6df | |||
| fdb400d5f0 | |||
| 0e66eb2859 | |||
| bb5f91d399 | |||
| 2964c391b5 | |||
| 60c99bca40 | |||
| 58affe3e97 |
192
README.md
192
README.md
@@ -1,8 +1,5 @@
|
||||
# Alfa Bank SDK (Unofficial)
|
||||
|
||||
[](https://github.com/apoloz/alfa-bank-sdk/actions)
|
||||
[](https://phpstan.org/)
|
||||
|
||||
Профессиональная PHP-библиотека для интеграции с API Альфа-Банка (BaaS и СБП). Построена на базе [Saloon v3](https://docs.saloon.dev) и [Spatie Laravel Data](https://spatie.be/docs/laravel-data).
|
||||
|
||||
## Требования
|
||||
@@ -11,6 +8,119 @@
|
||||
- [Saloon](https://docs.saloon.dev) v3.0+
|
||||
- [Spatie Laravel Data](https://spatie.be/docs/laravel-data)
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
### Настройка `.env`
|
||||
|
||||
Добавьте следующие переменные в ваш файл `.env`. Вам понадобятся сертификаты (PEM) и ключи.
|
||||
|
||||
```dotenv
|
||||
# --- BaaS (Номинальные счета, Выплаты) ---
|
||||
ALFA_BAAS_ENVIRONMENT=sandbox # или production
|
||||
ALFA_BAAS_CLIENT_ID=your-client-id
|
||||
ALFA_BAAS_CLIENT_SECRET=your-client-secret
|
||||
# URL в вашем приложении, на который вернется пользователь после входа (должен совпадать с настройками в Портале)
|
||||
ALFA_BAAS_REDIRECT_URI=https://your-app.com/alfa/callback
|
||||
ALFA_BAAS_CERT_PATH=storage/certs/baas_cert.pem
|
||||
ALFA_BAAS_KEY_PATH=storage/certs/baas_key.pem
|
||||
# Путь к корневым сертификатам (или 'false' для отключения проверки SSL в sandbox)
|
||||
ALFA_BAAS_CA_BUNDLE=false
|
||||
|
||||
# Опционально: Ключи для CMS-подписи (если отличаются от транспортных)
|
||||
# ALFA_BAAS_SIGNING_CERT_PATH=storage/certs/sign_cert.pem
|
||||
# ALFA_BAAS_SIGNING_KEY_PATH=storage/certs/sign_key.pem
|
||||
|
||||
# --- SBP (СБП, QR-коды) ---
|
||||
ALFA_SBP_ENVIRONMENT=sandbox # или production
|
||||
ALFA_SBP_ALIAS=your-key-name-alias
|
||||
ALFA_SBP_CERT_PATH=storage/certs/sbp_cert.pem
|
||||
ALFA_SBP_KEY_PATH=storage/certs/sbp_key.pem
|
||||
ALFA_SBP_CA_BUNDLE=false
|
||||
|
||||
# --- Webhooks ---
|
||||
ALFA_WEBHOOK_USER=alfa
|
||||
ALFA_WEBHOOK_PASSWORD=secret
|
||||
# Для проверки JWS-подписи вебхуков (опционально)
|
||||
ALFA_WEBHOOK_PUBLIC_KEY=storage/certs/alfa_public_key.pem
|
||||
```
|
||||
|
||||
### Файл конфигурации (`config/alfa-bank.php`)
|
||||
|
||||
Вот как выглядит структура конфигурационного файла по умолчанию. Вы можете использовать его для тонкой настройки, например, чтобы задать пути динамически.
|
||||
|
||||
```php
|
||||
return [
|
||||
'baas' => [
|
||||
'environment' => env('ALFA_BAAS_ENVIRONMENT', 'sandbox'),
|
||||
'client_id' => env('ALFA_BAAS_CLIENT_ID'),
|
||||
'client_secret' => env('ALFA_BAAS_CLIENT_SECRET'),
|
||||
'redirect_uri' => env('ALFA_BAAS_REDIRECT_URI'),
|
||||
'certificate_path' => env('ALFA_BAAS_CERT_PATH'),
|
||||
'private_key_path' => env('ALFA_BAAS_KEY_PATH'),
|
||||
'ca_bundle' => env('ALFA_BAAS_CA_BUNDLE'),
|
||||
// ...
|
||||
],
|
||||
'sbp' => [
|
||||
'environment' => env('ALFA_SBP_ENVIRONMENT', 'sandbox'),
|
||||
'key_name_alias' => env('ALFA_SBP_ALIAS'),
|
||||
'certificate_path' => env('ALFA_SBP_CERT_PATH'),
|
||||
'private_key_path' => env('ALFA_SBP_KEY_PATH'),
|
||||
'ca_bundle' => env('ALFA_SBP_CA_BUNDLE'),
|
||||
// ...
|
||||
],
|
||||
'webhook' => [
|
||||
'user' => env('ALFA_WEBHOOK_USER'),
|
||||
'password' => env('ALFA_WEBHOOK_PASSWORD'),
|
||||
'alfa_public_key' => env('ALFA_WEBHOOK_PUBLIC_KEY'),
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Подключение и Безопасность
|
||||
|
||||
SDK поддерживает две разные модели безопасности, используемые в API Альфа-Банка.
|
||||
@@ -60,6 +170,68 @@ $signature = CmsSigner::sign($payload, $privateKey, $certificate);
|
||||
// $signature помещается в массив digestSignatures
|
||||
```
|
||||
|
||||
### 3. Авторизация Authorization Code Flow (H2H)
|
||||
|
||||
SDK использует нативный функционал Saloon для OAuth2.
|
||||
|
||||
**1. Инициализация:**
|
||||
```php
|
||||
$baas = AlfaBank::baas(
|
||||
clientId: '...', clientSecret: '...', redirectUri: '...',
|
||||
certificatePath: '...', privateKeyPath: '...'
|
||||
);
|
||||
```
|
||||
|
||||
**2. Получение ссылки на вход:**
|
||||
```php
|
||||
$url = $baas->getAuthorizationUrl(['beneficiaries']);
|
||||
// Перенаправьте пользователя на $url
|
||||
```
|
||||
|
||||
**3. Обмен кода на токен:**
|
||||
```php
|
||||
$authenticator = $baas->getAccessToken($code);
|
||||
$baas->authenticate($authenticator); // Применить токен
|
||||
// Сохраните $authenticator->getRefreshToken() в БД
|
||||
```
|
||||
|
||||
**4. Обновление токена:**
|
||||
```php
|
||||
$newAuth = $baas->refreshAccessToken($savedRefreshToken);
|
||||
$baas->authenticate($newAuth);
|
||||
```
|
||||
|
||||
**Пример обработки callback в Laravel:**
|
||||
```php
|
||||
// routes/web.php
|
||||
Route::get('/alfa/callback', function (Request $request, AlfaBankConnector $baas) {
|
||||
$code = $request->query('code');
|
||||
|
||||
// Обмениваем временный код на постоянные токены
|
||||
$authenticator = $baas->getAccessToken($code);
|
||||
|
||||
// Сохраняем refresh_token в БД для будущих обновлений сессии
|
||||
$refreshToken = $authenticator->getRefreshToken();
|
||||
auth()->user()->update(['alfa_refresh_token' => $refreshToken]);
|
||||
|
||||
return "Авторизация в Альфа-Банке прошла успешно!";
|
||||
});
|
||||
```
|
||||
|
||||
Полный пример: [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)
|
||||
|
||||
---
|
||||
|
||||
## Архитектура и Потоки данных
|
||||
@@ -119,10 +291,12 @@ $data = new IdentifyRequestData(
|
||||
accountNumber: '40702810...',
|
||||
amountDistribution: AmountDistributionData::collect([
|
||||
['beneficiaryId' => 'BEN-001', 'amount' => '100.00']
|
||||
], DataCollection::class),
|
||||
digestSignatures: ... // CMS подпись
|
||||
], DataCollection::class)
|
||||
);
|
||||
|
||||
// Автоматическая подпись данных (использует сертификаты из конфигурации)
|
||||
$data->sign($baas->getSigner());
|
||||
|
||||
AlfaBank::send(new IdentifyTransactionRequest($data));
|
||||
```
|
||||
|
||||
@@ -155,6 +329,14 @@ $uploadUrl = 'https://baas.alfabank.ru/filebox/...'; // Получено из м
|
||||
AlfaBank::send(new UploadFileRequest($uploadUrl, $file));
|
||||
```
|
||||
|
||||
## Сценарии для Laravel Tinker (Copy-Paste)
|
||||
|
||||
Эти примеры разработаны специально для быстрого запуска и тестирования в консоли `php artisan tinker`. Они содержат полные цепочки бизнес-логики.
|
||||
|
||||
* [17_laravel_tinker_scoring_creation.php](examples/17_laravel_tinker_scoring_creation.php) — **Скоринг и Создание Бенефициара**: Полный цикл проверки и регистрации контрагента с автоматической подписью данных.
|
||||
* [18_laravel_tinker_identification_payout.php](examples/18_laravel_tinker_identification_payout.php) — **Идентификация и Выплата**: Привязка входящих денег к бенефициару и последующий вывод на карту (с шифрованием PAN).
|
||||
* [19_laravel_tinker_card_registration_payout.php](examples/19_laravel_tinker_card_registration_payout.php) — **Безопасная регистрация карт**: Токенизация карты (создание Реквизита) и выплата по ID (PCI DSS Friendly).
|
||||
|
||||
---
|
||||
|
||||
## Продвинутые возможности
|
||||
|
||||
@@ -6,35 +6,61 @@ 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";
|
||||
$baas->login(
|
||||
|
||||
// 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-uuid',
|
||||
// clientSecret: 'your-client-secret-string'
|
||||
// );
|
||||
|
||||
// METHOD B: Authorization Code Flow (H2H) (REQUIRED for Nominal Accounts)
|
||||
// See examples/13_h2h_auth_service.php for the full setup
|
||||
|
||||
// We re-initialize with auth credentials if we didn't pass them initially
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: __DIR__ . '/certs/client.cer',
|
||||
privateKeyPath: __DIR__ . '/certs/private.key',
|
||||
environment: Environment::SANDBOX,
|
||||
clientId: 'your-client-id',
|
||||
clientSecret: 'your-client-secret'
|
||||
clientSecret: 'your-client-secret',
|
||||
redirectUri: 'https://...'
|
||||
);
|
||||
echo "Authentication Successful!\n";
|
||||
|
||||
// Now you can use OAuth methods:
|
||||
// $url = $baas->getAuthorizationUrl();
|
||||
// or
|
||||
// $baas->refreshAccessToken('saved_refresh_token');
|
||||
|
||||
echo "Authentication Configured!\n";
|
||||
|
||||
// 2. Create a Beneficiary (Counterparty)
|
||||
echo "Creating Beneficiary...\n";
|
||||
|
||||
|
||||
// In a real app, use the DTOs
|
||||
// $payload = new CreateBeneficiaryRequestData(...);
|
||||
// $baas->send(new CreateBeneficiaryRequest($payload));
|
||||
|
||||
|
||||
echo "Beneficiary Request Sent (Mock).\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
if ($e instanceof \Apoloz\AlfaBank\Exceptions\AlfaBankException) {
|
||||
echo "API Code: " . $e->getErrorCode() . "\n";
|
||||
echo 'API Code: ' . $e->getErrorCode() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||||
use Apoloz\AlfaBank\Sbp\Data\QrCodeRequestData;
|
||||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||||
|
||||
// 1. Initialize SBP
|
||||
$sbp = AlfaBank::sbp(
|
||||
@@ -30,8 +30,8 @@ try {
|
||||
|
||||
if ($response->successful()) {
|
||||
$qrData = $response->dto();
|
||||
echo "QR Payload: " . $qrData->payload . "\n";
|
||||
echo "QR Image URL: " . $qrData->image . "\n";
|
||||
echo 'QR Payload: ' . $qrData->payload . "\n";
|
||||
echo 'QR Image URL: ' . $qrData->image . "\n";
|
||||
} else {
|
||||
echo "Failed to generate QR.\n";
|
||||
}
|
||||
@@ -41,5 +41,5 @@ try {
|
||||
// foreach($paginator as $item) { ... }
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
echo 'Error: ' . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
@@ -7,44 +7,43 @@ use Apoloz\AlfaBank\Enums\Environment;
|
||||
|
||||
/**
|
||||
* This example shows how to initialize the SDK with the Sandbox files provided by the bank.
|
||||
*
|
||||
*
|
||||
* IMPORTANT: You must first convert the .pfx file to .pem format using OpenSSL:
|
||||
*
|
||||
* 1. Extract signing key:
|
||||
*
|
||||
* 1. Extract signing key:
|
||||
* openssl pkcs12 -in certificate_for_requests_sign_with_password_1234.pfx -nocerts -out signing_key.pem -nodes -password pass:1234
|
||||
*
|
||||
*
|
||||
* 2. Extract signing certificate:
|
||||
* openssl pkcs12 -in certificate_for_requests_sign_with_password_1234.pfx -clcerts -nokeys -out signing_cert.pem -password pass:1234
|
||||
*
|
||||
*
|
||||
* 3. Ensure transport certs are in PEM format if they aren't already.
|
||||
*/
|
||||
|
||||
// --- 1. BaaS / Main API Initialization ---
|
||||
$baas = AlfaBank::baas(
|
||||
// Transport (mTLS) - use the sandbox_cert/key files
|
||||
certificatePath: __DIR__ . '/certs/sandbox_cert_2026.cer',
|
||||
certificatePath: __DIR__ . '/certs/sandbox_cert_2026.cer',
|
||||
privateKeyPath: __DIR__ . '/certs/sandbox_key_2026.key',
|
||||
|
||||
|
||||
// Signing (Business Logic) - use the extracted PEM files from PFX
|
||||
signingCertificatePath: __DIR__ . '/certs/signing_cert.pem',
|
||||
signingPrivateKeyPath: __DIR__ . '/certs/signing_key.pem',
|
||||
|
||||
|
||||
environment: Environment::SANDBOX,
|
||||
caBundlePath: __DIR__ . '/certs/apica_2022_chain.cer' // Trust chain
|
||||
);
|
||||
|
||||
echo "BaaS Connector Initialized.\n";
|
||||
|
||||
|
||||
// --- 2. SBP (Faster Payments) Initialization ---
|
||||
$sbp = AlfaBank::sbp(
|
||||
// SBP usually uses its own mTLS pair found in the sbp_payout/mtls folder
|
||||
certificatePath: __DIR__ . '/certs/sbp_payout/mtls/9728109955_test_mtls.crt',
|
||||
privateKeyPath: __DIR__ . '/certs/sbp_payout/mtls/9728109955_test_mtls.key',
|
||||
|
||||
|
||||
// Terminal ID from terminal_num.txt
|
||||
keyNameAlias: '9728109955',
|
||||
|
||||
keyNameAlias: '9728109955',
|
||||
|
||||
environment: Environment::SANDBOX,
|
||||
caBundlePath: __DIR__ . '/certs/sbp_payout/sandbox_root/root-ca-test.crt'
|
||||
);
|
||||
@@ -52,4 +51,3 @@ $sbp = AlfaBank::sbp(
|
||||
echo "SBP Connector Initialized.\n";
|
||||
|
||||
// Now you can use $baas->login(...) or $sbp->send(...)
|
||||
|
||||
|
||||
@@ -3,52 +3,49 @@
|
||||
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\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\CreateBeneficiaryRequest;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryRequestData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\AddressData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
/**
|
||||
* Example: Creating a Beneficiary for a Nominal Account.
|
||||
* This flow requires a digital signature (CMS).
|
||||
*/
|
||||
|
||||
// 1. Initialize
|
||||
// In Laravel Tinker: $baas = app(\Apoloz\AlfaBank\Core\AlfaBankConnector::class);
|
||||
// In Standalone:
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: 'path/to/cert.pem',
|
||||
privateKeyPath: 'path/to/key.pem',
|
||||
environment: Environment::SANDBOX
|
||||
certificatePath: __DIR__ . '/certs/client.cer',
|
||||
privateKeyPath: __DIR__ . '/certs/private.key',
|
||||
environment: Environment::SANDBOX,
|
||||
clientId: getenv('ALFA_BAAS_CLIENT_ID'),
|
||||
clientSecret: getenv('ALFA_BAAS_CLIENT_SECRET')
|
||||
);
|
||||
|
||||
// 1. Prepare beneficiary data
|
||||
// 2. Auth
|
||||
// $baas->login(); // Or refresh token logic
|
||||
|
||||
// 3. Prepare Payload
|
||||
$payload = new CreateBeneficiaryRequestData(
|
||||
type: 'FL_RESIDENT',
|
||||
countryOfTaxResidence: 'RU',
|
||||
name: 'Ivanov Ivan Ivanovich',
|
||||
inn: '771234567890',
|
||||
addresses: AddressData::collect([
|
||||
[
|
||||
'type' => 'REGISTRATION_ADDRESS',
|
||||
'address' => 'Moscow, Lenina st, 10',
|
||||
'country' => 'RU'
|
||||
]
|
||||
], DataCollection::class),
|
||||
digestSignatures: DigestSignatureData::collect([
|
||||
[
|
||||
'base64Encoded' => 'MOCK_SIGNATURE', // In real life, use CmsSigner::sign()
|
||||
'signatureType' => 'cms'
|
||||
]
|
||||
], DataCollection::class)
|
||||
name: 'Ivanov Ivan',
|
||||
addresses: new DataCollection(AddressData::class, [
|
||||
new AddressData('REGISTRATION_ADDRESS', 'Moscow', 'RU'),
|
||||
])
|
||||
// Note: digestSignatures is now optional!
|
||||
);
|
||||
|
||||
// 2. Login and send request
|
||||
$baas->login('client-id', 'client-secret');
|
||||
// 4. Sign Payload (New DX!)
|
||||
$payload->sign($baas->getSigner());
|
||||
|
||||
// 5. Send
|
||||
$response = $baas->send(new CreateBeneficiaryRequest($payload));
|
||||
|
||||
echo 'Status: ' . $response->status() . "\n";
|
||||
echo 'ID: ' . $response->dto()->id . "\n";
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->dto(); // BeneficiaryResponseData
|
||||
echo "Beneficiary creation accepted. ID: {$data->id}, Status: {$data->status}\n";
|
||||
} else {
|
||||
echo "Error: " . $response->body() . "\n";
|
||||
echo 'Error: ' . $response->body() . "\n";
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Webhooks\WebhookFactory;
|
||||
use Apoloz\AlfaBank\Requests\Webhooks\GetWebhooksRequest;
|
||||
use Apoloz\AlfaBank\Requests\Webhooks\CreateWebhookRequest;
|
||||
use Apoloz\AlfaBank\Data\Webhooks\Subscriptions\WebhookSubscriptionRequestData;
|
||||
use Apoloz\AlfaBank\Requests\Webhooks\CreateWebhookRequest;
|
||||
use Apoloz\AlfaBank\Requests\Webhooks\GetWebhooksRequest;
|
||||
use Apoloz\AlfaBank\Webhooks\WebhookFactory;
|
||||
|
||||
/**
|
||||
* 1. Managing Webhooks (BaaS API)
|
||||
@@ -15,7 +15,7 @@ $baas = AlfaBank::baas('cert.pem', 'key.pem');
|
||||
$baas->login('id', 'secret');
|
||||
|
||||
// List existing webhooks
|
||||
$response = $baas->send(new GetWebhooksRequest());
|
||||
$response = $baas->send(new GetWebhooksRequest);
|
||||
$webhooks = $response->dto(); // DataCollection of WebhookSubscriptionData
|
||||
|
||||
foreach ($webhooks as $webhook) {
|
||||
@@ -38,9 +38,9 @@ $rawPayload = [
|
||||
'transactionId' => 'TX-12345',
|
||||
'amount' => ['amount' => 100.50, 'currencyName' => 'RUR'],
|
||||
'direction' => 'CREDIT',
|
||||
'paymentPurpose' => 'Test payment'
|
||||
]
|
||||
]
|
||||
'paymentPurpose' => 'Test payment',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Use the factory to parse the payload into a DTO
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Requests\B2C\CreateB2CPaymentRequest;
|
||||
use Apoloz\AlfaBank\Data\B2C\B2CPaymentRequestData;
|
||||
use Apoloz\AlfaBank\Data\B2C\B2CClientData;
|
||||
use Apoloz\AlfaBank\Data\B2C\B2CPaymentRecipientData;
|
||||
use Apoloz\AlfaBank\Data\B2C\B2CPaymentRequestData;
|
||||
use Apoloz\AlfaBank\Requests\B2C\CreateB2CPaymentRequest;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
/**
|
||||
* Example: B2C Payout via SBP (Phone Number).
|
||||
*/
|
||||
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: 'path/to/cert.pem',
|
||||
privateKeyPath: 'path/to/key.pem'
|
||||
@@ -29,8 +28,8 @@ $payload = new B2CPaymentRequestData(
|
||||
'phone' => '79001112233',
|
||||
'amount' => 100.00,
|
||||
'nspkBankId' => '100000000001',
|
||||
'sequence' => 'unique-payout-id-1'
|
||||
]
|
||||
'sequence' => 'unique-payout-id-1',
|
||||
],
|
||||
], DataCollection::class)
|
||||
);
|
||||
|
||||
@@ -41,6 +40,5 @@ if ($response->successful()) {
|
||||
$data = $response->dto(); // B2CPaymentResponseData
|
||||
echo "Payout request sent. Status: {$data->status}\n";
|
||||
} else {
|
||||
echo "Error: " . $response->body() . "\n";
|
||||
echo 'Error: ' . $response->body() . "\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Utils\CmsSigner;
|
||||
use Apoloz\AlfaBank\Requests\Registries\CreateRegistryRequest;
|
||||
use Apoloz\AlfaBank\Data\Registries\RegistryRequestData;
|
||||
use Apoloz\AlfaBank\Data\Registries\RegistryPaymentData;
|
||||
use Apoloz\AlfaBank\Requests\Registries\SignRegistryRequest;
|
||||
use Apoloz\AlfaBank\Data\Registries\RegistryRequestData;
|
||||
use Apoloz\AlfaBank\Data\Registries\RegistrySignatureData;
|
||||
use Apoloz\AlfaBank\Requests\Registries\CreateRegistryRequest;
|
||||
use Apoloz\AlfaBank\Requests\Registries\SignRegistryRequest;
|
||||
use Apoloz\AlfaBank\Utils\CmsSigner;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
/**
|
||||
* Example: Creating and Signing a Payment Registry (Batch Payments).
|
||||
*/
|
||||
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: 'path/to/cert.pem',
|
||||
privateKeyPath: 'path/to/key.pem'
|
||||
@@ -31,8 +30,8 @@ $payload = new RegistryRequestData(
|
||||
'payeeInn' => '770...',
|
||||
'payeeName' => 'Vendor 1',
|
||||
'payerAccount' => '40702...',
|
||||
'purpose' => 'Payment for goods'
|
||||
]
|
||||
'purpose' => 'Payment for goods',
|
||||
],
|
||||
], DataCollection::class)
|
||||
);
|
||||
|
||||
@@ -43,7 +42,7 @@ $registryId = $response->dto()->id;
|
||||
echo "Registry Created: $registryId\n";
|
||||
|
||||
// 2. Sign Registry
|
||||
// In real life, you might need to sign the digest provided by the bank
|
||||
// In real life, you might need to sign the digest provided by the bank
|
||||
// or the registry content itself. This depends on the specific bank requirement.
|
||||
$signature = CmsSigner::sign(
|
||||
$registryId, // Example data
|
||||
@@ -55,11 +54,10 @@ $signatures = RegistrySignatureData::collect([
|
||||
[
|
||||
'base64Encoded' => $signature,
|
||||
'signatureType' => 'RSA',
|
||||
'certificateUuid' => 'YOUR_CERT_UUID'
|
||||
]
|
||||
'certificateUuid' => 'YOUR_CERT_UUID',
|
||||
],
|
||||
], DataCollection::class);
|
||||
|
||||
$baas->send(new SignRegistryRequest($registryId, $signatures));
|
||||
|
||||
echo "Registry Signed.\n";
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -23,6 +22,8 @@ use Spatie\LaravelData\DataCollection;
|
||||
*/
|
||||
|
||||
// 1. Инициализация SDK
|
||||
// Сертификаты (Transport Certs):
|
||||
// Получены от банка (Sandbox) или сгенерированы вами и активированы банком (Production).
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: 'transport.crt',
|
||||
privateKeyPath: 'transport.key'
|
||||
@@ -33,42 +34,41 @@ $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
|
||||
// client_id: Из Портала разработчика
|
||||
// client_secret: Из Портала разработчика (глаз) или сгенерирован через SDK (examples/14_generate_client_secret.php)
|
||||
$auth = AlfaBank::auth(
|
||||
'client_id', 'client_secret', 'https://callback',
|
||||
'transport.crt', 'transport.key'
|
||||
);
|
||||
$auth->refreshToken('saved_refresh_token_from_db');
|
||||
|
||||
// --- ЭТАП 1: Создание Бенефициара (Контрагента) ---
|
||||
echo "1. Регистрация бенефициара...\n";
|
||||
|
||||
$beneficiaryData = new CreateBeneficiaryRequestData(
|
||||
$benData = new CreateBeneficiaryRequestData(
|
||||
type: 'FL_RESIDENT',
|
||||
countryOfTaxResidence: 'RU',
|
||||
name: 'Иванов Иван',
|
||||
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'
|
||||
new AddressData('REGISTRATION_ADDRESS', 'Moscow', 'RU'),
|
||||
])
|
||||
);
|
||||
|
||||
$response = $baas->send(new CreateBeneficiaryRequest($beneficiaryData));
|
||||
$beneficiaryId = $response->dto()->requestId; // В некоторых версиях API возвращается UUID
|
||||
echo "Бенефициар создан. ID запроса: $beneficiaryId\n";
|
||||
// Автоматическая подпись
|
||||
$benData->sign($baas->getSigner());
|
||||
|
||||
$response = $baas->send(new CreateBeneficiaryRequest($benData));
|
||||
$beneficiaryId = $response->dto()->id;
|
||||
echo "Beneficiary Created: $beneficiaryId\n";
|
||||
|
||||
// --- ЭТАП 2: Верификация (Скоринг) ---
|
||||
// --- ЭТАП 2: Идентификация Входящего Платежа ---
|
||||
// После создания банк должен проверить бенефициара
|
||||
echo "2. Запуск верификации (скоринга)...";
|
||||
echo '2. Запуск верификации (скоринга)...';
|
||||
$baas->send(new ScoreBeneficiaryRequest($beneficiaryId));
|
||||
echo "Скоринг инициирован.\n";
|
||||
|
||||
|
||||
// --- ЭТАП 3: Выплата (Payoff) ---
|
||||
// Когда бенефициар проверен, можно делать выплату
|
||||
echo "3. Создание выплаты...";
|
||||
echo '3. Создание выплаты...';
|
||||
|
||||
$payoffData = new PayoffRequestData(
|
||||
type: 'PAYMENT_ORDER',
|
||||
@@ -87,14 +87,14 @@ try {
|
||||
digestSignatures: DigestSignatureData::collect([
|
||||
[
|
||||
'base64Encoded' => '...', // Здесь должна быть подпись тела выплаты
|
||||
'signatureType' => 'cms'
|
||||
]
|
||||
'signatureType' => 'cms',
|
||||
],
|
||||
], DataCollection::class)
|
||||
);
|
||||
|
||||
$payoffResponse = $baas->send(new CreatePayoffRequest($payoffData));
|
||||
echo "Выплата создана. ID: " . $payoffResponse->dto()->payoffId . "\n";
|
||||
echo 'Выплата создана. ID: ' . $payoffResponse->dto()->payoffId . "\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "Ошибка: " . $e->getMessage() . "\n";
|
||||
echo 'Ошибка: ' . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
@@ -5,15 +5,14 @@ declare(strict_types=1);
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Data\SelfEmployed\CreateIncomeRequestData;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\SelfEmployed\RegisterIncomeRequest;
|
||||
use Apoloz\AlfaBank\Data\SelfEmployed\CreateIncomeRequestData;
|
||||
|
||||
/**
|
||||
* Пример регистрации дохода самозанятого.
|
||||
* Позволяет автоматически задекларировать доход в ФНС от лица самозанятого.
|
||||
*/
|
||||
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: __DIR__ . '/../tests/test_cert.pem',
|
||||
privateKeyPath: __DIR__ . '/../tests/test_key.pem',
|
||||
@@ -37,6 +36,5 @@ try {
|
||||
echo "ID запроса: {$dto->requestId}\n";
|
||||
echo "Текущий статус: {$dto->status}\n";
|
||||
} catch (\Exception $e) {
|
||||
echo "Ошибка: " . $e->getMessage() . "\n";
|
||||
echo 'Ошибка: ' . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ use Apoloz\AlfaBank\Sbp\Requests\Reversal\GetQrcReversalHistoryRequest;
|
||||
* Пример получения истории возвратов по транзакции СБП.
|
||||
* Полезно для отладки частичных возвратов или поиска зависших операций.
|
||||
*/
|
||||
|
||||
$sbp = AlfaBank::sbp(
|
||||
certificatePath: __DIR__ . '/../tests/test_cert.pem',
|
||||
privateKeyPath: __DIR__ . '/../tests/test_key.pem',
|
||||
@@ -27,7 +26,7 @@ try {
|
||||
$response = $sbp->send(new GetQrcReversalHistoryRequest($payrrn, $termNo));
|
||||
$dto = $response->dto();
|
||||
|
||||
echo "Найдено возвратов: " . ($dto->refunds ? $dto->refunds->count() : 0) . "\n";
|
||||
echo 'Найдено возвратов: ' . ($dto->refunds ? $dto->refunds->count() : 0) . "\n";
|
||||
|
||||
if ($dto->refunds) {
|
||||
foreach ($dto->refunds as $refund) {
|
||||
@@ -38,6 +37,5 @@ try {
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo "Ошибка: " . $e->getMessage() . "\n";
|
||||
echo 'Ошибка: ' . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ use Apoloz\AlfaBank\Requests\Files\UploadFileRequest;
|
||||
* Сначала создается бенефициар, банк возвращает ссылку для загрузки паспорта.
|
||||
* Затем SDK загружает файл по этой ссылке.
|
||||
*/
|
||||
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: __DIR__ . '/../tests/test_cert.pem',
|
||||
privateKeyPath: __DIR__ . '/../tests/test_key.pem',
|
||||
@@ -34,13 +33,12 @@ try {
|
||||
if ($response->status() === 200) {
|
||||
echo "Файл успешно загружен в банк!\n";
|
||||
} else {
|
||||
echo "Ошибка загрузки: " . $response->body() . "\n";
|
||||
echo 'Ошибка загрузки: ' . $response->body() . "\n";
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo "Ошибка: " . $e->getMessage() . "\n";
|
||||
echo 'Ошибка: ' . $e->getMessage() . "\n";
|
||||
} finally {
|
||||
if (is_resource($fileResource)) {
|
||||
fclose($fileResource);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
92
examples/12_auth_code_flow.php
Normal file
92
examples/12_auth_code_flow.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?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 = [
|
||||
// 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:
|
||||
// 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...',
|
||||
];
|
||||
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
82
examples/13_h2h_auth_service.php
Normal file
82
examples/13_h2h_auth_service.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?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'); // 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. Инициализация SDK (сразу с кредами)
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: $cert,
|
||||
privateKeyPath: $key,
|
||||
environment: Environment::SANDBOX,
|
||||
clientId: $clientId,
|
||||
clientSecret: $clientSecret,
|
||||
redirectUri: $redirectUri
|
||||
);
|
||||
|
||||
// --- СЦЕНАРИЙ 1: Генерация ссылки на вход ---
|
||||
// Используем нативный метод Saloon
|
||||
$link = $baas->getAuthorizationUrl(scopes: ['beneficiaries', 'payment']);
|
||||
echo "1. Перейдите по ссылке для авторизации:\n$link\n\n";
|
||||
|
||||
// --- СЦЕНАРИЙ 2: Обмен кода на токен (в callback-скрипте) ---
|
||||
// $code приходит в GET-параметре: /callback?code=...
|
||||
$codeFromUrl = '...код из браузера...';
|
||||
|
||||
if ($codeFromUrl !== '...код из браузера...') {
|
||||
try {
|
||||
echo "2. Обмен кода на токены...\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 в базу данных!
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo 'Ошибка обмена кода: ' . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// --- СЦЕНАРИЙ 3: Регулярное обновление токена (Cron / Middleware) ---
|
||||
$savedRefreshToken = '...saved_refresh_token...';
|
||||
|
||||
if ($savedRefreshToken !== '...saved_refresh_token...') {
|
||||
try {
|
||||
echo "3. Обновление токена...\n";
|
||||
|
||||
// Обновляем токен
|
||||
$newAuthenticator = $baas->refreshAccessToken($savedRefreshToken);
|
||||
|
||||
// Автоматически применяем к коннектору (для текущего скрипта)
|
||||
$baas->authenticate($newAuthenticator);
|
||||
|
||||
echo "Токен обновлен!\n";
|
||||
|
||||
// Если пришел новый refresh_token — обновляем его
|
||||
if ($newAuthenticator->getRefreshToken()) {
|
||||
// DB::update(...)
|
||||
echo "Новый Refresh Token получен.\n";
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo 'Ошибка обновления токена: ' . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
60
examples/14_generate_client_secret.php
Normal file
60
examples/14_generate_client_secret.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\Auth\RefreshClientSecretRequest;
|
||||
|
||||
/**
|
||||
* 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` (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
|
||||
$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";
|
||||
}
|
||||
}
|
||||
74
examples/15_nominal_accounts_list.php
Normal file
74
examples/15_nominal_accounts_list.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* FINAL GOAL EXAMPLE: Get List of Nominal Account Beneficiaries
|
||||
*
|
||||
* Usage:
|
||||
* 1. Standalone: php examples/15_nominal_accounts_list.php
|
||||
* 2. Laravel Tinker: Copy-paste the code below (replace config with app() helper).
|
||||
*
|
||||
* This script assumes you have already:
|
||||
* 1. Generated/Received Certificates.
|
||||
* 2. Obtained Client ID & Secret (via Portal or Script 14).
|
||||
* 3. Passed H2H Auth via Browser and obtained a Refresh Token (via Script 13).
|
||||
*/
|
||||
|
||||
// --- CONFIGURATION (From .env) ---
|
||||
$config = [
|
||||
'client_id' => 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";
|
||||
}
|
||||
89
examples/16_automated_startup.php
Normal file
89
examples/16_automated_startup.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\Auth\RefreshClientSecretRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\GetBeneficiariesRequest;
|
||||
|
||||
/**
|
||||
* FULLY AUTOMATED WORKFLOW
|
||||
*
|
||||
* Scenario: You don't want to store `client_secret` persistently.
|
||||
* Instead, you use your Certificates to fetch a fresh secret on startup (or when needed),
|
||||
* and then immediately use it to refresh your OAuth tokens.
|
||||
*
|
||||
* CAUTION: Requesting a new secret invalidates the old one.
|
||||
* If you have multiple app instances, implement a shared cache (Redis/DB) for the secret!
|
||||
*/
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
// You only need immutable identifiers and certificates.
|
||||
$clientId = getenv('ALFA_BAAS_CLIENT_ID');
|
||||
$redirectUri = 'https://your-app.com/callback';
|
||||
$certPath = __DIR__ . '/certs/client.cer';
|
||||
$keyPath = __DIR__ . '/certs/private.key';
|
||||
$accNum = '40702810...';
|
||||
|
||||
// This assumes you have already passed the browser flow once and saved the refresh token.
|
||||
$savedRefreshToken = 'your_saved_refresh_token_from_db';
|
||||
|
||||
// 1. Initialize Bare Connector (mTLS only)
|
||||
$baas = AlfaBank::baas($certPath, $keyPath, Environment::SANDBOX);
|
||||
|
||||
try {
|
||||
// 2. FETCH CLIENT SECRET DYNAMICALLY
|
||||
// We prove our identity via mTLS certificates.
|
||||
echo "1. Requesting dynamic client_secret via API...\n";
|
||||
|
||||
// In a real app: CHECK CACHE FIRST!
|
||||
// $cachedSecret = Cache::get('alfa_client_secret');
|
||||
// if ($cachedSecret) { $tempSecret = $cachedSecret; } else { ... }
|
||||
|
||||
$secretResponse = $baas->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";
|
||||
}
|
||||
}
|
||||
97
examples/17_laravel_tinker_scoring_creation.php
Normal file
97
examples/17_laravel_tinker_scoring_creation.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* LARAVEL TINKER WORKFLOW: Scoring -> Creation -> Signing
|
||||
*
|
||||
* This script is designed to be copy-pasted directly into `php artisan tinker`.
|
||||
* It demonstrates the new simplified DX with automatic signing.
|
||||
*
|
||||
* Prerequisites:
|
||||
* 1. .env is configured (ALFA_BAAS_CLIENT_ID, ALFA_BAAS_CERT_PATH, etc.)
|
||||
* 2. Run: php artisan tinker
|
||||
*/
|
||||
|
||||
use Apoloz\AlfaBank\Core\AlfaBankConnector;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\AddressData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryRequestData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\ScoreBeneficiaryRequestData;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\CreateBeneficiaryRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\ScoreBeneficiaryRequest;
|
||||
|
||||
// 1. Get Configured Connector
|
||||
// Laravel automatically injects config from config/alfa-bank.php
|
||||
$baas = app(AlfaBankConnector::class);
|
||||
|
||||
// 2. Authenticate
|
||||
// Uses Client Credentials (if configured) or Refresh Token logic if you have one.
|
||||
// For server-to-server tasks, login() is usually sufficient.
|
||||
echo "Logging in...\n";
|
||||
$baas->login();
|
||||
|
||||
// --- STEP A: SCORING (Check if we can work with this person) ---
|
||||
echo "--- Step A: Scoring ---\n";
|
||||
|
||||
$scoreData = new ScoreBeneficiaryRequestData(
|
||||
type: 'FL_RESIDENT',
|
||||
inn: '771234567890',
|
||||
name: 'Иванов Иван Иванович'
|
||||
);
|
||||
|
||||
try {
|
||||
$scoreResponse = $baas->send(new ScoreBeneficiaryRequest($scoreData));
|
||||
$scoreResult = $scoreResponse->dto(); // ScoreBeneficiaryResponseData
|
||||
|
||||
echo 'Scoring Result: ' . $scoreResult->result . "\n";
|
||||
echo 'Description: ' . $scoreResult->description . "\n";
|
||||
|
||||
if ($scoreResult->result !== 'SUCCESS') {
|
||||
echo "Stopping: Scoring failed.\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo 'Scoring Error: ' . $e->getMessage() . "\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// --- STEP B: CREATION (With Automatic Signing) ---
|
||||
echo "\n--- Step B: Creation ---\n";
|
||||
|
||||
$createData = new CreateBeneficiaryRequestData(
|
||||
type: 'FL_RESIDENT',
|
||||
countryOfTaxResidence: 'RU',
|
||||
name: 'Иванов Иван Иванович', // Must match scoring
|
||||
inn: '771234567890',
|
||||
phoneNumber: '79001234567',
|
||||
email: 'ivanov@example.com',
|
||||
addresses: AddressData::collect([
|
||||
new AddressData(
|
||||
type: 'REGISTRATION_ADDRESS',
|
||||
address: '123456, г. Москва, ул. Тверская, д. 1',
|
||||
country: 'RU'
|
||||
),
|
||||
])
|
||||
);
|
||||
|
||||
// MAGIC HAPPENS HERE:
|
||||
// Automatically signs the JSON payload using the connector's certificates.
|
||||
echo "Signing data...\n";
|
||||
$createData->sign($baas->getSigner());
|
||||
|
||||
try {
|
||||
$createResponse = $baas->send(new CreateBeneficiaryRequest($createData));
|
||||
$beneficiary = $createResponse->dto(); // CreateBeneficiaryResponseData
|
||||
|
||||
echo "SUCCESS! Beneficiary Created.\n";
|
||||
echo 'ID: ' . $beneficiary->id . "\n";
|
||||
echo 'Status: ' . $beneficiary->status . "\n";
|
||||
echo 'Request ID: ' . $beneficiary->requestId . "\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo 'Creation Error: ' . $e->getMessage() . "\n";
|
||||
if (method_exists($e, 'getResponse')) {
|
||||
echo 'Response Body: ' . $e->getResponse()->body() . "\n";
|
||||
}
|
||||
}
|
||||
102
examples/18_laravel_tinker_identification_payout.php
Normal file
102
examples/18_laravel_tinker_identification_payout.php
Normal file
@@ -0,0 +1,102 @@
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\AmountDistributionData;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Payouts\PayoffRequest;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\PayoffRequestData;
|
||||
use Apoloz\AlfaBank\Data\Common\RecipientData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\PayoutRequisiteData;
|
||||
use Apoloz\AlfaBank\Services\PhpseclibCardEncryptor;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
// Replace these with real data from your system/webhook
|
||||
$nominalAccount = '40702810...'; // Ваш Номинальный счет
|
||||
$transactionId = '1220112MOCO...'; // ID входящего платежа (из Вебхука или Выписки)
|
||||
$beneficiaryId = 'BEN-8d4a...'; // ID Бенефициара (которого создали в примере 17)
|
||||
$amount = '100.00'; // Сумма операции (в рублях)
|
||||
// Public key for CARD encryption (Download from Bank Portal)
|
||||
$cardPublicKeyPath = storage_path('certs/alfa_card_enc_key.pub');
|
||||
|
||||
// 1. Setup Connector
|
||||
$baas = app(AlfaBankConnector::class);
|
||||
$baas->login();
|
||||
|
||||
echo "Connector ready.\n";
|
||||
|
||||
// ==========================================
|
||||
// STEP 1: IDENTIFICATION (Assign funds to Beneficiary)
|
||||
// ==========================================
|
||||
echo "\n--- Step 1: Identification ---\n";
|
||||
|
||||
$identifyData = new IdentifyRequestData(
|
||||
transactionId: $transactionId,
|
||||
accountNumber: $nominalAccount,
|
||||
amountDistribution: AmountDistributionData::collect([
|
||||
[
|
||||
'beneficiaryId' => $beneficiaryId,
|
||||
'amount' => $amount
|
||||
]
|
||||
], DataCollection::class)
|
||||
);
|
||||
|
||||
// Automatic Signing
|
||||
$identifyData->sign($baas->getSigner());
|
||||
|
||||
try {
|
||||
$idResponse = $baas->send(new IdentifyTransactionRequest($identifyData));
|
||||
echo "Identification Status: " . $idResponse->status() . "\n";
|
||||
echo "Funds assigned to beneficiary.\n";
|
||||
} catch (\Exception $e) {
|
||||
echo "Identification Failed: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
|
||||
// ==========================================
|
||||
// STEP 2: PAYOUT (Withdraw to Bank Card)
|
||||
// ==========================================
|
||||
echo "\n--- Step 2: Payout (Card) ---\n";
|
||||
|
||||
// A. Encrypt the Card Number (PAN)
|
||||
// This is required for security. The raw PAN is never sent.
|
||||
if (!file_exists($cardPublicKeyPath)) {
|
||||
echo "Error: Card Encryption Key not found at $cardPublicKeyPath\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$encryptor = new PhpseclibCardEncryptor(file_get_contents($cardPublicKeyPath));
|
||||
$encryptedPan = $encryptor->encrypt('2200000000000000'); // Real PAN here
|
||||
|
||||
echo "PAN Encrypted.\n";
|
||||
|
||||
// B. Prepare Payout Request
|
||||
$payoutData = new PayoffRequestData(
|
||||
type: 'FL_RESIDENT',
|
||||
accountNumber: $nominalAccount,
|
||||
recipient: new RecipientData($beneficiaryId),
|
||||
amount: $amount,
|
||||
purpose: 'Выплата средств по договору оферты',
|
||||
|
||||
// Direct details (Bank Card / PAN)
|
||||
payoutRequisite: new PayoutRequisiteData(
|
||||
type: 'CARD',
|
||||
pan: $encryptedPan
|
||||
)
|
||||
);
|
||||
|
||||
// C. Sign and Send
|
||||
echo "Signing payout request...\n";
|
||||
$payoutData->sign($baas->getSigner());
|
||||
|
||||
try {
|
||||
$payResponse = $baas->send(new PayoffRequest($payoutData));
|
||||
$result = $payResponse->dto();
|
||||
|
||||
echo "Payout Initiated!\n";
|
||||
echo "Transaction ID: " . ($result->id ?? 'N/A') . "\n";
|
||||
echo "Status: " . ($result->status ?? 'PENDING') . "\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "Payout Failed: " . $e->getMessage() . "\n";
|
||||
if (method_exists($e, 'getResponse')) {
|
||||
echo "Details: " . $e->getResponse()->body() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
107
examples/19_laravel_tinker_card_registration_payout.php
Normal file
107
examples/19_laravel_tinker_card_registration_payout.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* LARAVEL TINKER WORKFLOW: Secure Card Registration -> Payout by ID
|
||||
*
|
||||
* Scenario:
|
||||
* 1. User enters Card Number (PAN).
|
||||
* 2. You encrypt PAN and register it as a "Requisite" in Alfa Bank.
|
||||
* 3. You save only the `requisiteId` in your database.
|
||||
* 4. Later, you pay out to this user using only the ID (Zero PAN storage).
|
||||
*
|
||||
* Usage: Copy-paste into `php artisan tinker`.
|
||||
*/
|
||||
|
||||
use Apoloz\AlfaBank\Core\AlfaBankConnector;
|
||||
use Apoloz\AlfaBank\Data\Common\RecipientData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites\CreateRequisiteRequestData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\PayoffRequestData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\PayoutRequisiteData;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Beneficiaries\Requisites\CreateRequisiteRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Payouts\PayoffRequest;
|
||||
use Apoloz\AlfaBank\Services\PhpseclibCardEncryptor;
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
$nominalAccount = '40702810...';
|
||||
$beneficiaryId = 'BEN-123...';
|
||||
$rawPan = '4276000000001234'; // Вводится пользователем (не сохранять в БД!)
|
||||
$cardPubKeyPath = storage_path('certs/alfa_card_enc_key.pub');
|
||||
|
||||
// 1. Setup
|
||||
$baas = app(AlfaBankConnector::class);
|
||||
$baas->login();
|
||||
|
||||
// ==========================================
|
||||
// STEP 1: SECURE REGISTRATION (Tokenization)
|
||||
// ==========================================
|
||||
echo '--- Step 1: Registering Card Requisite ---
|
||||
';
|
||||
|
||||
// A. Encrypt PAN for transport
|
||||
$encryptor = new PhpseclibCardEncryptor(file_get_contents($cardPubKeyPath));
|
||||
$encryptedPan = $encryptor->encrypt($rawPan);
|
||||
|
||||
// B. Request Creation of Requisite in Alfa Bank
|
||||
$reqData = new CreateRequisiteRequestData(
|
||||
type: 'CARD',
|
||||
pan: $encryptedPan,
|
||||
beneficiaryId: $beneficiaryId,
|
||||
isMain: true
|
||||
);
|
||||
|
||||
$reqData->sign($baas->getSigner());
|
||||
|
||||
try {
|
||||
$reqResponse = $baas->send(new CreateRequisiteRequest($reqData));
|
||||
$requisiteId = $reqResponse->dto()->id; // E.g. "REQ-998877"
|
||||
|
||||
echo 'SUCCESS! Card Registered.
|
||||
';
|
||||
echo "Requisite ID: $requisiteId (SAVE THIS TO DATABASE)
|
||||
";
|
||||
echo 'Mask: ' . substr($rawPan, 0, 6) . '******' . substr($rawPan, -4) . '
|
||||
';
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo 'Registration Failed: ' . $e->getMessage() . '
|
||||
';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// STEP 2: PAYOUT (Using Requisite ID)
|
||||
// ==========================================
|
||||
echo "\n--- Step 2: Payout using Requisite ID ---
|
||||
";
|
||||
|
||||
// Now we don't need PAN anymore. We use the ID we just got.
|
||||
$payoutData = new PayoffRequestData(
|
||||
type: 'FL_RESIDENT',
|
||||
accountNumber: $nominalAccount,
|
||||
recipient: new RecipientData($beneficiaryId),
|
||||
amount: '100.00',
|
||||
purpose: 'Выплата вознаграждения',
|
||||
|
||||
// Instead of PAN, we provide the Requisite ID
|
||||
payoutRequisite: new PayoutRequisiteData(
|
||||
type: 'CARD',
|
||||
requisiteId: $requisiteId
|
||||
)
|
||||
);
|
||||
|
||||
$payoutData->sign($baas->getSigner());
|
||||
|
||||
try {
|
||||
$payResponse = $baas->send(new PayoffRequest($payoutData));
|
||||
$result = $payResponse->dto();
|
||||
|
||||
echo 'Payout Sent!
|
||||
';
|
||||
echo 'ID: ' . ($result->id ?? 'N/A') . '
|
||||
';
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo 'Payout Failed: ' . $e->getMessage() . '
|
||||
';
|
||||
}
|
||||
271
examples/20_sbp_qr_to_nominal_account.php
Normal file
271
examples/20_sbp_qr_to_nominal_account.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Пример: SBP QR → Номинальный счёт → Идентификация → Бенефициар
|
||||
*
|
||||
* Сценарий:
|
||||
* 1. Генерируем SBP QR код для пополнения номинального счёта
|
||||
* 2. Клиент оплачивает через QR (переводит деньги на номинальный счёт)
|
||||
* 3. Получаем список неидентифицированных пополнений
|
||||
* 4. Идентифицируем платёж (привязываем к конкретному бенефициару)
|
||||
* 5. Проверяем статус идентификации
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apoloz\AlfaBank\AlfaBank;
|
||||
use Apoloz\AlfaBank\Core\AlfaConfig;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\AmountDistributionData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\IdentifyRequestData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\GetIdentifyStatusRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\GetUnidentifiedTransactionsRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\IdentifyTransactionRequest;
|
||||
use Apoloz\AlfaBank\Sbp\Data\QrCodeRequestData;
|
||||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||||
use Apoloz\AlfaBank\Sbp\SbpConnector;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
echo "=== SBP QR → Номинальный счёт → Идентификация ===\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// КОНФИГУРАЦИЯ
|
||||
// =============================================================================
|
||||
|
||||
// SBP QR конфигурация (mTLS + подпись)
|
||||
$sbpMtlsCert = __DIR__ . '/certs/sbp_mtls.crt';
|
||||
$sbpMtlsKey = __DIR__ . '/certs/sbp_mtls.key';
|
||||
$sbpSigningKey = __DIR__ . '/certs/sbp_signing.key'; // 4096-bit для подписи
|
||||
$sbpKeyAlias = 'your-alias-from-bank';
|
||||
$sbpTermNo = '30003970'; // Номер терминала
|
||||
$sbpCaBundle = __DIR__ . '/certs/russian_trusted_ca-bundle.crt';
|
||||
|
||||
// BaaS конфигурация (для номинальных счетов)
|
||||
$baasCert = __DIR__ . '/certs/baas_transport.crt';
|
||||
$baasKey = __DIR__ . '/certs/baas_transport.key';
|
||||
$baasClientId = getenv('ALFA_BAAS_CLIENT_ID');
|
||||
$baasClientSecret = getenv('ALFA_BAAS_CLIENT_SECRET');
|
||||
$baasCaBundle = __DIR__ . '/certs/russian_trusted_ca-bundle.crt';
|
||||
|
||||
// Данные для идентификации
|
||||
$nominalAccountNumber = '40702810XXXXXXXXXXX'; // Ваш номинальный счёт
|
||||
$beneficiaryId = '3fa85f64-5717-4562-b3fc-2c963f66afa6'; // ID бенефициара
|
||||
|
||||
echo "Конфигурация:\n";
|
||||
echo " Номинальный счёт: $nominalAccountNumber\n";
|
||||
echo " Бенефициар ID: $beneficiaryId\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ЭТАП 1: Генерация SBP QR кода для пополнения номинального счёта
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ЭТАП 1: Генерация SBP QR кода ===\n";
|
||||
|
||||
try {
|
||||
// Создаём SBP connector с раздельными ключами (mTLS + signing)
|
||||
$sbpConfig = new AlfaConfig(
|
||||
baseUrl: Environment::SBP_SANDBOX->value,
|
||||
certificatePath: $sbpMtlsCert,
|
||||
privateKeyPath: $sbpMtlsKey,
|
||||
caBundlePath: $sbpCaBundle,
|
||||
signingPrivateKeyPath: $sbpSigningKey // 4096-bit ключ для подписи
|
||||
);
|
||||
|
||||
$sbp = new SbpConnector($sbpConfig, $sbpKeyAlias);
|
||||
|
||||
// Создаём динамический QR код с уникальным OrderNumber для идентификации
|
||||
$orderNumber = 'ORDER-' . uniqid();
|
||||
|
||||
$qrPayload = new QrCodeRequestData(
|
||||
termNo: $sbpTermNo,
|
||||
qrcType: '02', // Dynamic QR
|
||||
amount: '50000', // 500.00 RUB
|
||||
currency: 'RUB',
|
||||
paymentPurpose: "Пополнение номинального счета {$nominalAccountNumber}",
|
||||
orderNumber: $orderNumber, // Уникальный номер заказа для отслеживания
|
||||
messageID: 'sbp-' . uniqid(),
|
||||
qrTtl: '60', // QR активен 60 минут
|
||||
notificationUrl: 'https://your-domain.com/webhooks/sbp', // Webhook для уведомлений
|
||||
width: '300',
|
||||
height: '300'
|
||||
);
|
||||
|
||||
$request = new GetQrCodeRequest($qrPayload);
|
||||
$response = $sbp->send($request);
|
||||
$qrData = $response->dto();
|
||||
|
||||
if ($qrData->ErrorCode !== 0) {
|
||||
throw new \RuntimeException("QR generation failed: {$qrData->message}");
|
||||
}
|
||||
|
||||
echo "✅ QR код сгенерирован!\n";
|
||||
echo " Order Number: $orderNumber\n";
|
||||
echo " QRC ID: {$qrData->qrcId}\n";
|
||||
echo " QR Payload: {$qrData->payload}\n";
|
||||
echo " Status: {$qrData->status}\n";
|
||||
echo "\n💡 Клиент должен отсканировать QR и оплатить {$qrData->payload}\n\n";
|
||||
|
||||
// Сохраняем QR изображение для показа клиенту
|
||||
if (! empty($qrData->image)) {
|
||||
$qrImagePath = __DIR__ . '/qr_payment.png';
|
||||
file_put_contents($qrImagePath, base64_decode($qrData->image));
|
||||
echo " 📁 QR изображение сохранено: $qrImagePath\n\n";
|
||||
}
|
||||
|
||||
echo "⏳ Ждём, пока клиент оплатит...\n";
|
||||
echo " (В тестовой среде используйте Sandbox для эмуляции оплаты)\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ЭТАП 2: Получение неидентифицированных пополнений
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ЭТАП 2: Поиск неидентифицированных пополнений ===\n";
|
||||
echo " (После оплаты через SBP деньги поступают на номинальный счёт\n";
|
||||
echo " как 'неидентифицированное пополнение')\n\n";
|
||||
|
||||
// Инициализируем BaaS connector (требуется H2H авторизация)
|
||||
$baas = AlfaBank::baas(
|
||||
certificatePath: $baasCert,
|
||||
privateKeyPath: $baasKey,
|
||||
environment: Environment::SANDBOX,
|
||||
clientId: $baasClientId,
|
||||
clientSecret: $baasClientSecret
|
||||
);
|
||||
|
||||
// Авторизация через Client Credentials Flow
|
||||
$baas->login($baasClientId, $baasClientSecret, ['beneficiary-payments']);
|
||||
|
||||
// Получаем список неидентифицированных пополнений
|
||||
$unidentifiedRequest = new GetUnidentifiedTransactionsRequest(
|
||||
accountNumber: $nominalAccountNumber,
|
||||
limit: 10,
|
||||
offset: 0
|
||||
);
|
||||
|
||||
$unidentifiedResponse = $baas->send($unidentifiedRequest);
|
||||
$unidentifiedCollection = $unidentifiedResponse->dto();
|
||||
|
||||
echo 'Найдено неидентифицированных пополнений: ' . count($unidentifiedCollection->items) . "\n\n";
|
||||
|
||||
if (count($unidentifiedCollection->items) === 0) {
|
||||
echo "⚠️ Нет неидентифицированных пополнений. Возможные причины:\n";
|
||||
echo " - Платёж ещё не поступил (нужно время на обработку)\n";
|
||||
echo " - Неправильный номер номинального счёта\n";
|
||||
echo " - Платёж уже идентифицирован\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Ищем нужную транзакцию (обычно по сумме или назначению платежа)
|
||||
$targetTransaction = null;
|
||||
foreach ($unidentifiedCollection->items as $transaction) {
|
||||
echo " Транзакция ID: {$transaction->id}\n";
|
||||
echo " Сумма: {$transaction->amount} RUB\n";
|
||||
echo " Назначение: {$transaction->purpose}\n";
|
||||
echo " Дата: {$transaction->date}\n";
|
||||
echo " Плательщик: {$transaction->payer?->name}\n\n";
|
||||
|
||||
// В реальном сценарии ищем по сумме, назначению или другим критериям
|
||||
// Здесь берём первую для примера
|
||||
if ($targetTransaction === null) {
|
||||
$targetTransaction = $transaction;
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetTransaction === null) {
|
||||
throw new \RuntimeException('Не найдена подходящая транзакция для идентификации');
|
||||
}
|
||||
|
||||
echo "✅ Выбрана транзакция для идентификации: {$targetTransaction->id}\n";
|
||||
echo " Сумма: {$targetTransaction->amount} RUB\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ЭТАП 3: Идентификация платежа (привязка к бенефициару)
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ЭТАП 3: Идентификация платежа ===\n";
|
||||
echo " Привязываем платёж к бенефициару: $beneficiaryId\n\n";
|
||||
|
||||
// Подготавливаем распределение суммы
|
||||
// Можно распределить между несколькими бенефициарами
|
||||
$amountDistribution = DataCollection::create([
|
||||
new AmountDistributionData(
|
||||
beneficiaryId: $beneficiaryId,
|
||||
amount: $targetTransaction->amount
|
||||
),
|
||||
], AmountDistributionData::class);
|
||||
|
||||
// Подготавливаем данные для идентификации
|
||||
$identifyData = new IdentifyRequestData(
|
||||
transactionId: $targetTransaction->id,
|
||||
accountNumber: $nominalAccountNumber,
|
||||
amountDistribution: $amountDistribution,
|
||||
// Подпись добавляется через sign() метод
|
||||
digestSignatures: DataCollection::create([
|
||||
new DigestSignatureData(
|
||||
base64Encoded: 'base64-encoded-signature-here', // Реальная подпись
|
||||
signatureType: 'rsa'
|
||||
),
|
||||
], DigestSignatureData::class)
|
||||
);
|
||||
|
||||
// Создаём запрос на идентификацию
|
||||
$identifyRequest = new IdentifyTransactionRequest(
|
||||
id: $targetTransaction->id,
|
||||
data: $identifyData
|
||||
);
|
||||
|
||||
$identifyResponse = $baas->send($identifyRequest);
|
||||
$identifyResult = $identifyResponse->dto();
|
||||
|
||||
echo "✅ Запрос на идентификацию создан!\n";
|
||||
echo " Identify ID: {$identifyResult->identifyId}\n";
|
||||
echo " Статус: {$identifyResult->status}\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ЭТАП 4: Проверка статуса идентификации
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ЭТАП 4: Проверка статуса ===\n\n";
|
||||
|
||||
$statusRequest = new GetIdentifyStatusRequest($identifyResult->identifyId);
|
||||
$statusResponse = $baas->send($statusRequest);
|
||||
$statusResult = $statusResponse->dto();
|
||||
|
||||
echo "Статус идентификации: {$statusResult->status}\n";
|
||||
echo "Возможные статусы:\n";
|
||||
echo " - PENDING: В обработке\n";
|
||||
echo " - VALIDATED: Провалидировано\n";
|
||||
echo " - COMPLETE: Успешно завершено\n";
|
||||
echo " - FAILED: Ошибка\n\n";
|
||||
|
||||
if ($statusResult->status === 'COMPLETE') {
|
||||
echo "🎉 ИДЕНТИФИКАЦИЯ УСПЕШНО ЗАВЕРШЕНА!\n";
|
||||
echo " Деньги {$targetTransaction->amount} RUB зачислены бенефициару $beneficiaryId\n\n";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// РЕЗЮМЕ
|
||||
// =============================================================================
|
||||
|
||||
echo "=== РЕЗЮМЕ ===\n";
|
||||
echo "✅ 1. Сгенерирован SBP QR код\n";
|
||||
echo "✅ 2. Клиент оплатил через SBP (эмитация в тесте)\n";
|
||||
echo "✅ 3. Деньги поступили на номинальный счёт\n";
|
||||
echo "✅ 4. Платёж идентифицирован и привязан к бенефициару\n";
|
||||
echo "\n💡 Теперь бенефициар может распоряжаться этими средствами\n";
|
||||
echo " (например, запросить выплату через CreatePayoffRequest)\n";
|
||||
|
||||
} catch (\Apoloz\AlfaBank\Exceptions\AlfaBankException $e) {
|
||||
echo "\n❌ AlfaBankException: {$e->getMessage()}\n";
|
||||
if ($e->getResponse()) {
|
||||
echo "Ответ API: {$e->getResponse()->body()}\n";
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo "\n❌ Ошибка: {$e->getMessage()}\n";
|
||||
echo "Файл: {$e->getFile()}:{$e->getLine()}\n";
|
||||
}
|
||||
|
||||
echo "\n=== Пример завершён ===\n";
|
||||
179
examples/21_laravel_tinker_sbp_identification.php
Normal file
179
examples/21_laravel_tinker_sbp_identification.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Laravel Tinker: SBP QR → Номинальный счёт → Идентификация
|
||||
*
|
||||
* Использование:
|
||||
* docker compose exec php-fpm php artisan tinker
|
||||
* >>> include 'examples/21_laravel_tinker_sbp_identification.php'
|
||||
*/
|
||||
|
||||
use Apoloz\AlfaBank\Core\AlfaBankConnector;
|
||||
use Apoloz\AlfaBank\Core\AlfaConfig;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\AmountDistributionData;
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Identification\IdentifyRequestData;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\GetIdentifyStatusRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\GetUnidentifiedTransactionsRequest;
|
||||
use Apoloz\AlfaBank\Requests\NominalAccounts\Identification\IdentifyTransactionRequest;
|
||||
use Apoloz\AlfaBank\Sbp\Data\QrCodeRequestData;
|
||||
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
||||
use Apoloz\AlfaBank\Sbp\SbpConnector;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
echo "=== SBP QR → Номинальный счёт → Идентификация (Tinker) ===\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// КОНФИГУРАЦИЯ
|
||||
// =============================================================================
|
||||
|
||||
$nominalAccountNumber = '40702810601880001584'; // Номинальный счёт из sandbox
|
||||
$beneficiaryId = 'your-beneficiary-id-here'; // ID бенефициара (получить из списка)
|
||||
$amount = '15000'; // 150.00 RUB в копейках
|
||||
|
||||
echo "Номинальный счёт: $nominalAccountNumber\n";
|
||||
echo 'Сумма: ' . number_format($amount / 100, 2) . " RUB\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ШАГ 1: Генерация SBP QR
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ШАГ 1: Генерация SBP QR кода ===\n";
|
||||
|
||||
try {
|
||||
// SBP Connector
|
||||
$sbpConfig = new AlfaConfig(
|
||||
baseUrl: Environment::SBP_SANDBOX->value,
|
||||
certificatePath: config('alfa-bank.sbp.certificate_path'),
|
||||
privateKeyPath: config('alfa-bank.sbp.private_key_path'),
|
||||
caBundlePath: config('alfa-bank.sbp.ca_bundle'),
|
||||
signingPrivateKeyPath: config('alfa-bank.sbp.signing_private_key_path')
|
||||
);
|
||||
|
||||
$sbp = new SbpConnector($sbpConfig, config('alfa-bank.sbp.key_name_alias'));
|
||||
|
||||
$orderNumber = 'ORDER-' . uniqid();
|
||||
|
||||
$qrResponse = $sbp->send(new GetQrCodeRequest(
|
||||
new QrCodeRequestData(
|
||||
termNo: config('alfa-bank.sbp.terminal_no'),
|
||||
qrcType: '02', // Dynamic QR
|
||||
amount: $amount,
|
||||
currency: 'RUB',
|
||||
paymentPurpose: "Пополнение счета $nominalAccountNumber",
|
||||
orderNumber: $orderNumber,
|
||||
messageID: 'sbp-' . uniqid(),
|
||||
qrTtl: '60'
|
||||
)
|
||||
));
|
||||
|
||||
$qrData = $qrResponse->dto();
|
||||
|
||||
if ($qrData->ErrorCode !== 0) {
|
||||
echo "❌ Ошибка генерации QR: {$qrData->message}\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo "✅ QR код сгенерирован!\n";
|
||||
echo " Order: $orderNumber\n";
|
||||
echo " QRC ID: {$qrData->qrcId}\n";
|
||||
echo " Payload: {$qrData->payload}\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ШАГ 2: Получение неидентифицированных пополнений
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ШАГ 2: Поиск неидентифицированных пополнений ===\n";
|
||||
echo "(После оплаты клиентом через SBP)\n\n";
|
||||
|
||||
$baas = app(AlfaBankConnector::class);
|
||||
|
||||
// Получаем список
|
||||
$unidentifiedResponse = $baas->send(
|
||||
new GetUnidentifiedTransactionsRequest($nominalAccountNumber, limit: 5)
|
||||
);
|
||||
|
||||
$transactions = $unidentifiedResponse->dto()->items;
|
||||
echo 'Найдено транзакций: ' . count($transactions) . "\n\n";
|
||||
|
||||
if (count($transactions) === 0) {
|
||||
echo "⚠️ Нет неидентифицированных пополнений\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем список
|
||||
foreach ($transactions as $i => $tx) {
|
||||
echo " [$i] ID: {$tx->id}\n";
|
||||
echo " Сумма: {$tx->amount} RUB\n";
|
||||
echo " Назначение: {$tx->purpose}\n";
|
||||
echo " Плательщик: {$tx->payer?->name}\n\n";
|
||||
}
|
||||
|
||||
// Берём первую для примера
|
||||
$transaction = $transactions[0];
|
||||
echo "✅ Выбрана транзакция: {$transaction->id} ({$transaction->amount} RUB)\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ШАГ 3: Идентификация (привязка к бенефициару)
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ШАГ 3: Идентификация платежа ===\n";
|
||||
|
||||
if (empty($beneficiaryId) || $beneficiaryId === 'your-beneficiary-id-here') {
|
||||
echo "⚠️ Укажите правильный beneficiaryId!\n";
|
||||
echo " Получите список бенефициаров через:\n";
|
||||
echo " >>> \$baas->send(new GetBeneficiariesRequest())->dto()\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$identifyData = new IdentifyRequestData(
|
||||
transactionId: $transaction->id,
|
||||
accountNumber: $nominalAccountNumber,
|
||||
amountDistribution: DataCollection::create([
|
||||
new AmountDistributionData(
|
||||
beneficiaryId: $beneficiaryId,
|
||||
amount: $transaction->amount
|
||||
),
|
||||
], AmountDistributionData::class)
|
||||
);
|
||||
|
||||
$identifyResponse = $baas->send(
|
||||
new IdentifyTransactionRequest($transaction->id, $identifyData)
|
||||
);
|
||||
|
||||
$result = $identifyResponse->dto();
|
||||
echo "✅ Запрос создан!\n";
|
||||
echo " Identify ID: {$result->identifyId}\n";
|
||||
echo " Статус: {$result->status}\n\n";
|
||||
|
||||
// =============================================================================
|
||||
// ШАГ 4: Проверка статуса
|
||||
// =============================================================================
|
||||
|
||||
echo "=== ШАГ 4: Проверка статуса ===\n";
|
||||
|
||||
$statusResponse = $baas->send(
|
||||
new GetIdentifyStatusRequest($result->identifyId)
|
||||
);
|
||||
|
||||
$status = $statusResponse->dto();
|
||||
echo "Текущий статус: {$status->status}\n\n";
|
||||
|
||||
echo "=== РЕЗУЛЬТАТ ===\n";
|
||||
echo "✅ SBP QR: {$qrData->payload}\n";
|
||||
echo "✅ Транзакция: {$transaction->id}\n";
|
||||
echo "✅ Бенефициар: $beneficiaryId\n";
|
||||
echo "✅ Сумма: {$transaction->amount} RUB\n";
|
||||
echo "✅ Статус идентификации: {$status->status}\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo "\n❌ Ошибка: {$e->getMessage()}\n";
|
||||
echo 'Класс: ' . get_class($e) . "\n";
|
||||
}
|
||||
|
||||
echo "\n=== Готово ===\n";
|
||||
38
examples/README.md
Normal file
38
examples/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Alfa Bank SDK Examples
|
||||
|
||||
This directory contains executable PHP scripts demonstrating various features of the SDK.
|
||||
|
||||
## How to Run
|
||||
|
||||
### 1. Standalone (CLI)
|
||||
You can run any script directly from the terminal if you have PHP installed.
|
||||
Ensure you have run `composer install` in the SDK root first.
|
||||
|
||||
```bash
|
||||
cd alfa-bank-sdk
|
||||
php examples/01_baas_flow.php
|
||||
```
|
||||
|
||||
### 2. Laravel Tinker
|
||||
Most examples are designed to be copy-paste friendly for `php artisan tinker`.
|
||||
|
||||
**General Steps:**
|
||||
1. Open your Laravel project terminal.
|
||||
2. Run `php artisan tinker`.
|
||||
3. Instead of the manual `AlfaBank::baas(...)` initialization, use the Laravel helper:
|
||||
```php
|
||||
$baas = app(\Apoloz\AlfaBank\Core\AlfaBankConnector::class);
|
||||
$baas->login(); // Or refresh token
|
||||
```
|
||||
4. Copy the rest of the logic (DTO creation, signing, sending) from the example file.
|
||||
|
||||
**Dedicated Tinker Workflow:**
|
||||
See `17_laravel_tinker_scoring_creation.php` for a complete, copy-paste ready workflow for creating beneficiaries.
|
||||
|
||||
## Key Examples
|
||||
|
||||
- **01_baas_flow.php**: Basic setup and connection check.
|
||||
- **13_h2h_auth_service.php**: How to get your first OAuth token.
|
||||
- **14_generate_client_secret.php**: If you lost your Client Secret.
|
||||
- **15_nominal_accounts_list.php**: Fetching lists (Beneficiaries).
|
||||
- **17_laravel_tinker_scoring_creation.php**: **START HERE** for Nominal Accounts integration.
|
||||
@@ -9,9 +9,9 @@ namespace Apoloz\AlfaBank;
|
||||
use Apoloz\AlfaBank\Core\AlfaBankConnector;
|
||||
use Apoloz\AlfaBank\Core\AlfaConfig;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Services\OpenSslCmsSigner;
|
||||
use Apoloz\AlfaBank\Sbp\SbpConnector;
|
||||
use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest;
|
||||
use Apoloz\AlfaBank\Sbp\SbpConnector;
|
||||
use Apoloz\AlfaBank\Services\OpenSslCmsSigner;
|
||||
|
||||
class AlfaBank
|
||||
{
|
||||
@@ -29,7 +29,8 @@ class AlfaBank
|
||||
): bool {
|
||||
$baas = self::baas($certificatePath, $privateKeyPath, $environment, $caBundlePath);
|
||||
$baas->login($clientId, $clientSecret);
|
||||
$baas->send(new GetAccountsRequest());
|
||||
$baas->send(new GetAccountsRequest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,6 +39,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 {
|
||||
@@ -46,6 +50,9 @@ class AlfaBank
|
||||
certificatePath: $certificatePath,
|
||||
privateKeyPath: $privateKeyPath,
|
||||
caBundlePath: $caBundlePath,
|
||||
clientId: $clientId,
|
||||
clientSecret: $clientSecret,
|
||||
redirectUri: $redirectUri,
|
||||
signingCertificatePath: $signingCertificatePath,
|
||||
signingPrivateKeyPath: $signingPrivateKeyPath
|
||||
);
|
||||
@@ -70,8 +77,8 @@ class AlfaBank
|
||||
?string $signingCertificatePath = null,
|
||||
?string $signingPrivateKeyPath = null
|
||||
): SbpConnector {
|
||||
$baseUrl = $environment === Environment::PRODUCTION
|
||||
? Environment::SBP_PRODUCTION->value
|
||||
$baseUrl = $environment === Environment::PRODUCTION
|
||||
? Environment::SBP_PRODUCTION->value
|
||||
: Environment::SBP_SANDBOX->value;
|
||||
|
||||
$config = new AlfaConfig(
|
||||
@@ -86,4 +93,4 @@ class AlfaBank
|
||||
return new SbpConnector($config, $keyNameAlias);
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,24 +7,27 @@ namespace Apoloz\AlfaBank\Core;
|
||||
// <SECTION name="module_imports" intent="Import necessary contracts, services, and base framework components for the connector's operation.">
|
||||
// <BELIEF_STATE "Saloon's plugin architecture requires importing traits for features like retries and timeouts. Local contracts (SignerInterface) are imported for dependency injection.">
|
||||
use Apoloz\AlfaBank\Contracts\SignerInterface;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Http\Middleware\RequestIdMiddleware;
|
||||
use Apoloz\AlfaBank\Http\Pagination\AlfaOffsetPaginator;
|
||||
use Apoloz\AlfaBank\Requests\Auth\GetTokenRequest;
|
||||
use Apoloz\AlfaBank\Traits\HasAlfaErrorHandling;
|
||||
use Saloon\Helpers\OAuth2\OAuthConfig;
|
||||
use Saloon\Http\Connector;
|
||||
use Saloon\Http\Request;
|
||||
use Saloon\PaginationPlugin\Contracts\HasPagination;
|
||||
use Saloon\PaginationPlugin\OffsetPaginator;
|
||||
use Saloon\Traits\OAuth2\AuthorizationCodeGrant;
|
||||
use Saloon\Traits\Plugins\AlwaysThrowOnErrors;
|
||||
use Saloon\Traits\RequestProperties\HasTries;
|
||||
use Saloon\Traits\Plugins\HasTimeout;
|
||||
use Saloon\Traits\RequestProperties\HasTries;
|
||||
|
||||
class AlfaBankConnector extends Connector implements HasPagination
|
||||
{
|
||||
use AlwaysThrowOnErrors;
|
||||
use AuthorizationCodeGrant;
|
||||
use HasAlfaErrorHandling;
|
||||
use HasTimeout;
|
||||
use HasTries;
|
||||
use AlwaysThrowOnErrors;
|
||||
use HasAlfaErrorHandling;
|
||||
|
||||
public function __construct(
|
||||
protected AlfaConfig $alfaConfig,
|
||||
@@ -39,28 +42,6 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
return $this->alfaConfig->baseUrl;
|
||||
}
|
||||
|
||||
protected function defaultConfig(): array
|
||||
{
|
||||
$config = [
|
||||
'cert' => $this->alfaConfig->certificatePath,
|
||||
'ssl_key' => $this->alfaConfig->privateKeyPath,
|
||||
'verify' => true,
|
||||
];
|
||||
|
||||
if ($this->alfaConfig->caBundlePath) {
|
||||
$config['verify'] = $this->alfaConfig->caBundlePath;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function defaultMiddleware(): array
|
||||
{
|
||||
return [
|
||||
new RequestIdMiddleware(),
|
||||
];
|
||||
}
|
||||
|
||||
public function boot(\Saloon\Http\PendingRequest $pendingRequest): void
|
||||
{
|
||||
//
|
||||
@@ -71,41 +52,45 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
return new AlfaOffsetPaginator($this, $request);
|
||||
}
|
||||
|
||||
public function login(?string $clientId = null, ?string $clientSecret = null): static
|
||||
// login() kept for ClientCredentials compatibility (legacy/simple flow)
|
||||
public function login(?string $clientId = null, ?string $clientSecret = null, array $scopes = []): static
|
||||
{
|
||||
// Allow overriding config via method arguments
|
||||
$clientId = $clientId ?? $this->alfaConfig->clientId;
|
||||
$clientSecret = $clientSecret ?? $this->alfaConfig->clientSecret;
|
||||
$oauthConfig = $this->oauthConfig();
|
||||
|
||||
if (!$clientId || !$clientSecret) {
|
||||
throw new \RuntimeException("Client ID/Secret not configured.");
|
||||
// Allow overriding credentials via method arguments
|
||||
if ($clientId) {
|
||||
$oauthConfig->setClientId($clientId);
|
||||
}
|
||||
if ($clientSecret) {
|
||||
$oauthConfig->setClientSecret($clientSecret);
|
||||
}
|
||||
|
||||
$request = new GetTokenRequest(
|
||||
clientId: $clientId,
|
||||
clientSecret: $clientSecret,
|
||||
grantType: 'client_credentials'
|
||||
);
|
||||
if (empty($oauthConfig->getClientId()) || empty($oauthConfig->getClientSecret())) {
|
||||
throw new \RuntimeException('Client ID/Secret not configured.');
|
||||
}
|
||||
|
||||
$request = new \Saloon\Http\OAuth2\GetClientCredentialsTokenRequest($oauthConfig, $scopes);
|
||||
|
||||
// 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();
|
||||
|
||||
$this->authenticate(new \Saloon\Http\Auth\TokenAuthenticator($data->access_token));
|
||||
|
||||
// Use the trait's helper to parse the token
|
||||
$authenticator = $this->createOAuthAuthenticatorFromResponse($response);
|
||||
|
||||
$this->authenticate($authenticator);
|
||||
|
||||
return $this;
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="signer_accessor" intent="Provide access to the configured signing service for requests that require a digital signature.">
|
||||
// <BELIEF_STATE "Requests should not instantiate their own signing logic. The connector acts as a service locator, providing the globally-configured Signer service. This ensures that all signatures are generated consistently.">
|
||||
public function getSigner(): SignerInterface
|
||||
{
|
||||
if (!$this->signer) {
|
||||
throw new \RuntimeException("No signer configured for this connector.");
|
||||
if (! $this->signer) {
|
||||
throw new \RuntimeException('No signer configured for this connector.');
|
||||
}
|
||||
|
||||
return $this->signer;
|
||||
}
|
||||
// </SECTION>
|
||||
@@ -116,7 +101,7 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
{
|
||||
$this->middleware()->onRequest(new \Apoloz\AlfaBank\Http\Middleware\LoggingRequestMiddleware($logger));
|
||||
$this->middleware()->onResponse(new \Apoloz\AlfaBank\Http\Middleware\LoggingResponseMiddleware($logger));
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -126,8 +111,53 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
public function withJwsVerification(string $publicKeyPem): static
|
||||
{
|
||||
$this->middleware()->onResponse(new \Apoloz\AlfaBank\Http\Middleware\VerifyJwsResponseMiddleware($publicKeyPem));
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function defaultOauthConfig(): 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 = [
|
||||
'cert' => $this->alfaConfig->certificatePath,
|
||||
'ssl_key' => $this->alfaConfig->privateKeyPath,
|
||||
'verify' => true,
|
||||
];
|
||||
|
||||
if ($this->alfaConfig->caBundlePath) {
|
||||
$config['verify'] = $this->alfaConfig->caBundlePath === 'false' ? false : $this->alfaConfig->caBundlePath;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function defaultHeaders(): array
|
||||
{
|
||||
return [
|
||||
'Accept' => 'application/json',
|
||||
// Content-Type не устанавливаем здесь, каждый Request сам определяет его через трейты (HasJsonBody, HasFormBody и т.д.)
|
||||
// OAuth токен запросы требуют application/x-www-form-urlencoded
|
||||
];
|
||||
}
|
||||
|
||||
protected function defaultMiddleware(): array
|
||||
{
|
||||
return [
|
||||
new RequestIdMiddleware,
|
||||
];
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,23 +33,25 @@ readonly class AlfaConfig
|
||||
public ?string $clientId = null,
|
||||
// <BELIEF_STATE "Optional: The OAuth client secret, required for token-based authentication.">
|
||||
public ?string $clientSecret = null,
|
||||
// <BELIEF_STATE "Optional: The OAuth redirect URI, required for authorization code flow.">
|
||||
public ?string $redirectUri = null,
|
||||
// <BELIEF_STATE "Optional: Path to the certificate used specifically for signing requests (JWS/CMS), if different from the transport certificate.">
|
||||
public ?string $signingCertificatePath = null,
|
||||
// <BELIEF_STATE "Optional: Path to the private key used specifically for signing requests (JWS/CMS), if different from the transport key.">
|
||||
public ?string $signingPrivateKeyPath = null,
|
||||
) {
|
||||
// <BELIEF_STATE "This runtime check ensures that the provided certificate and key paths are not just valid strings, but point to actual, accessible files on the filesystem. This prevents configuration errors that would otherwise only surface during an HTTP request.">
|
||||
if (!file_exists($this->certificatePath)) {
|
||||
if (! file_exists($this->certificatePath)) {
|
||||
throw new \InvalidArgumentException("Certificate not found: {$this->certificatePath}");
|
||||
}
|
||||
if (!file_exists($this->privateKeyPath)) {
|
||||
if (! file_exists($this->privateKeyPath)) {
|
||||
throw new \InvalidArgumentException("Private key not found: {$this->privateKeyPath}");
|
||||
}
|
||||
if ($this->signingCertificatePath && !file_exists($this->signingCertificatePath)) {
|
||||
throw new \InvalidArgumentException("Signing certificate not found: {$this->signingCertificatePath}");
|
||||
if ($this->signingCertificatePath && ! file_exists($this->signingCertificatePath)) {
|
||||
throw new \InvalidArgumentException("Signing certificate not found: {$this->signingCertificatePath}");
|
||||
}
|
||||
if ($this->signingPrivateKeyPath && !file_exists($this->signingPrivateKeyPath)) {
|
||||
throw new \InvalidArgumentException("Signing private key not found: {$this->signingPrivateKeyPath}");
|
||||
if ($this->signingPrivateKeyPath && ! file_exists($this->signingPrivateKeyPath)) {
|
||||
throw new \InvalidArgumentException("Signing private key not found: {$this->signingPrivateKeyPath}");
|
||||
}
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
@@ -13,6 +13,6 @@ class AccountData extends Data
|
||||
public string $openDate,
|
||||
public string $type,
|
||||
public ?string $mnemonic = null,
|
||||
public ?array $balance = null,
|
||||
public ?array $balance = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\Auth;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for OAuth 2.0 token responses.">
|
||||
|
||||
@@ -17,6 +17,7 @@ use Apoloz\AlfaBank\Data\PaginationData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a collection of client consents.">
|
||||
|
||||
@@ -14,9 +14,10 @@ namespace Apoloz\AlfaBank\Data\Auth\UserInfo;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO represents a single consent entry. The `PaginationData` import here is likely due to prior usage or template, but not directly used within this single DTO.">
|
||||
use Apoloz\AlfaBank\Data\PaginationData; // This import is likely extraneous for a single DTO, but kept for context consistency.
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf; // This is also likely extraneous for a single DTO.
|
||||
// This is also likely extraneous for a single DTO.
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection; // This is also likely extraneous for a single DTO.
|
||||
|
||||
// This is also likely extraneous for a single DTO.
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a single client consent.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\Auth\UserInfo;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for OpenID Connect UserInfo claims.">
|
||||
|
||||
@@ -4,9 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Data\B2C;
|
||||
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
class B2CPaymentResponseData extends Data
|
||||
{
|
||||
|
||||
@@ -14,4 +14,4 @@ class CertificateData extends Data
|
||||
public string $status,
|
||||
public ?string $thumbprint = null,
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Apoloz\AlfaBank\Data\FileExchange;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for file download response data.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\FileExchange;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for file link details.">
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Apoloz\AlfaBank\Data\FileExchange;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for file upload response data.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for address details.">
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a beneficiary's balance details.">
|
||||
|
||||
@@ -17,6 +17,7 @@ use Apoloz\AlfaBank\Data\PaginationData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the paginated collection for beneficiary transactions.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <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 individual transaction payloads.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the beneficiary transaction data structure.">
|
||||
|
||||
@@ -14,17 +14,21 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components for complex nested data structures.">
|
||||
// <BELIEF_STATE "This DTO has several nested DTOs and collections (`AddressData`, `DigestSignatureData`, `DocumentData`, `PersonalInformationData`). `DataCollectionOf` and `Data` ensure that these are all correctly typed and mapped.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Attributes\Validation\Email;
|
||||
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for creating a new beneficiary.">
|
||||
// <BELIEF_STATE "This DTO serves as a robust contract for the complex payload required to create a beneficiary. Type-safety here prevents malformed requests from even being constructed.">
|
||||
class CreateBeneficiaryRequestData extends Data
|
||||
{
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the beneficiary creation request, including nested DTOs and collections.">
|
||||
// <BELIEF_STATE "The constructor meticulously lists all possible fields, making it clear what data is expected. Mandatory fields are non-nullable, while optional ones are nullable. Collections like `addresses` and `digestSignatures` are type-hinted with `DataCollection` and use `#[DataCollectionOf]` to ensure each item is itself a DTO.">
|
||||
public function __construct(
|
||||
@@ -33,7 +37,7 @@ class CreateBeneficiaryRequestData extends Data
|
||||
#[DataCollectionOf(AddressData::class)]
|
||||
public DataCollection $addresses,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $accountNumber = null,
|
||||
public ?bool $taxResidentUS = null,
|
||||
public ?bool $foreignTaxResident = null,
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the response of creating a beneficiary.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for an identification document.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for personal identification details.">
|
||||
|
||||
@@ -14,21 +14,25 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO requires `DigestSignatureData` as a collection for security, utilizing `DataCollectionOf` for type safety.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for creating a new requisite.">
|
||||
// <BELIEF_STATE "This DTO ensures the request payload for creating a requisite is strictly typed and complete, preventing malformed requests for a critical financial operation.">
|
||||
class CreateRequisiteRequestData extends Data
|
||||
{
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the requisite creation request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory fields like `type` and `digestSignatures`. Optional fields cover various payment methods (`pan`, `phoneNumber`, `accountNumber`) and identifiers (`bankId`, `bik`, `name`, `inn`, `kpp`, `beneficiaryId`, `isMain`).">
|
||||
public function __construct(
|
||||
public string $type,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $beneficiaryId = null,
|
||||
public ?string $pan = null,
|
||||
public ?string $phoneNumber = null,
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a single payment requisite.">
|
||||
|
||||
@@ -14,22 +14,26 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO requires `DigestSignatureData` as a collection for security, utilizing `DataCollectionOf` for type safety.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for updating an existing requisite.">
|
||||
// <BELIEF_STATE "This DTO ensures the request payload for updating a requisite is strictly typed and complete, supporting partial updates through nullable fields.">
|
||||
// <BELIEF_STATE "This DTO provides a structured format for updating requisite details. It supports partial updates by making all fields except signatures optional.">
|
||||
class UpdateRequisiteRequestData extends Data
|
||||
{
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the requisite update request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory `digestSignatures` for security. Optional fields allow for specific updates to `pan`, `keyId`, `phoneNumber`, `bankId`, `accountNumber`, `bik`, `isMain` status, and `isActive` status. The flexibility with nullable fields allows partial updates.">
|
||||
public function __construct(
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $pan = null,
|
||||
public ?string $keyId = null, // Specific identifier for some requisite types
|
||||
public ?string $keyId = null,
|
||||
public ?string $phoneNumber = null,
|
||||
public ?string $bankId = null,
|
||||
public ?string $accountNumber = null,
|
||||
|
||||
@@ -14,31 +14,49 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO handles complex nested structures (`DigestSignatureData`, `AddressData`, `DocumentData`, `PersonalInformationData`), requiring `DataCollectionOf` for typed arrays.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Attributes\Validation\Email;
|
||||
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for updating an existing beneficiary.">
|
||||
// <BELIEF_STATE "This DTO provides a robust contract for updating beneficiary details. By making most fields nullable, it supports partial updates (PATCH-like behavior) even if the API uses PUT.">
|
||||
// <BELIEF_STATE "This DTO provides a structured format for updating beneficiary details. By mirroring the structure of the creation DTO but making most fields optional, it supports partial updates while maintaining type safety.">
|
||||
class UpdateBeneficiaryRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the beneficiary update request, including nested DTOs and collections.">
|
||||
// <BELIEF_STATE "Similar to `CreateBeneficiaryRequestData`, this constructor lists all updatable fields. Mandatory fields are `type` and `digestSignatures`. Most other fields are nullable to allow for partial updates, reflecting the flexibility often needed in update operations.">
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the beneficiary update request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory `digestSignatures`. Most other fields are nullable to allow for partial updates, reflecting the flexibility often needed in update operations.">
|
||||
public function __construct(
|
||||
public string $type,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?string $name = null,
|
||||
public ?string $registrationDate = null,
|
||||
public ?PersonalInformationData $personalInformation = null,
|
||||
public ?string $phoneNumber = null,
|
||||
public ?string $email = null,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $type = null,
|
||||
public ?string $countryOfTaxResidence = null,
|
||||
#[DataCollectionOf(AddressData::class)]
|
||||
public ?DataCollection $addresses = null,
|
||||
public ?string $accountNumber = null,
|
||||
public ?bool $taxResidentUS = null,
|
||||
public ?bool $foreignTaxResident = null,
|
||||
public ?bool $financialActivities = null,
|
||||
public ?string $name = null,
|
||||
public ?string $registrationDate = null,
|
||||
#[Numeric]
|
||||
public ?string $inn = null,
|
||||
#[Numeric]
|
||||
public ?string $kpp = null,
|
||||
#[Numeric]
|
||||
public ?string $ogrn = null,
|
||||
public ?PersonalInformationData $personalInformation = null,
|
||||
public ?string $phoneNumber = null,
|
||||
#[Email]
|
||||
public ?string $email = null,
|
||||
#[DataCollectionOf(DocumentData::class)]
|
||||
public ?DataCollection $documents = null,
|
||||
public ?bool $isActive = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a specific wallet's balance.">
|
||||
|
||||
@@ -17,6 +17,7 @@ use Apoloz\AlfaBank\Data\PaginationData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a collection of beneficiaries.">
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts;
|
||||
use Apoloz\AlfaBank\Data\PaginationData; // This import might be misleading for a single DTO, but kept for context consistency.
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a single beneficiary's details.">
|
||||
@@ -45,4 +45,4 @@ class BeneficiaryData extends Data
|
||||
public ?bool $isVpr = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the status of a counterparty request.">
|
||||
|
||||
@@ -17,6 +17,7 @@ use Apoloz\AlfaBank\Data\PaginationData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a collection of deals.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a single deal's details.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a payoff associated with a deal.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the status of a deal request.">
|
||||
|
||||
@@ -14,15 +14,19 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals\Steps;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components for complex nested data structures.">
|
||||
// <BELIEF_STATE "This DTO orchestrates several other DTOs and collections (`DigestSignatureData`, `ContractorAmountData`), requiring `DataCollectionOf` for typed arrays.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for performing an action on a deal contractor.">
|
||||
// <BELIEF_STATE "This DTO provides a robust contract for managing contractors within deal steps. It ensures that critical financial and security-related data is strictly typed and correctly structured for actions like adding funds or changing status.">
|
||||
class ContractorActionRequestData extends Data
|
||||
{
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the contractor action request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory fields like `action`, `dealId`, `stepId`, `beneficiaryId`, and `digestSignatures`. Optional fields like `amount`, `purpose`, `amounts` (for detailed distribution), `keepOnNominalAccount`, and `requisiteId` provide flexibility for various contractor management scenarios.">
|
||||
public function __construct(
|
||||
@@ -31,7 +35,7 @@ class ContractorActionRequestData extends Data
|
||||
public string $stepId,
|
||||
public string $beneficiaryId,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $amount = null,
|
||||
public ?string $purpose = null,
|
||||
#[DataCollectionOf(ContractorAmountData::class)]
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals\Steps;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for an amount allocated to a contractor's wallet.">
|
||||
|
||||
@@ -14,22 +14,26 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals\Steps;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO requires `DigestSignatureData` as a collection for security, utilizing `DataCollectionOf` for type safety.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for performing an action on a deal step.">
|
||||
// <BELIEF_STATE "This DTO provides a robust contract for managing the lifecycle of deal steps. It ensures that critical operational and security data is strictly typed and correctly structured for actions like completion or cancellation.">
|
||||
// <BELIEF_STATE "This DTO encapsulates the data required to transition a deal step to a new state (e.g., COMPLETE, CANCEL). It handles the action type, identifiers, and necessary signatures.">
|
||||
class StepActionRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the step action request.">
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define the properties of the step action request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory fields like `dealId`, `action` (e.g., 'COMPLETE', 'CANCEL'), and `digestSignatures`. Optional fields like `stepId` (if targeting a specific step within the deal) and `description` provide flexibility and context for the action.">
|
||||
public function __construct(
|
||||
public string $dealId,
|
||||
public string $action, // e.g., COMPLETE, CANCEL
|
||||
public string $action,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $stepId = null,
|
||||
public ?string $description = null,
|
||||
) {}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals\Steps;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the response of a step action request.">
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals\Steps;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a contractor in a deal step.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals\Steps;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a single deal step.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Deals;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the response of a deal validation.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for distributing amounts to beneficiaries.">
|
||||
|
||||
@@ -14,16 +14,20 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components for complex nested structures.">
|
||||
// <BELIEF_STATE "This DTO orchestrates several other DTOs and collections (`AmountDistributionData`, `DigestSignatureData`), requiring `DataCollectionOf` for typed arrays.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for identifying an unidentified transaction.">
|
||||
// <BELIEF_STATE "This DTO provides a robust contract for the complex payload required to identify a transaction. It ensures that critical financial and cryptographic data is correctly structured.">
|
||||
// <SECTION name="class_definition" intent="Define a DTO for identifying a transaction.">
|
||||
// <BELIEF_STATE "This DTO encapsulates the data required to link an incoming transaction to a specific beneficiary and distribute the funds. It serves as the payload for the IdentifyTransactionRequest. ">
|
||||
class IdentifyRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the transaction identification request.">
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define the properties of the identification request.">
|
||||
// <BELIEF_STATE "The constructor meticulously lists all mandatory fields. `transactionId` and `accountNumber` pinpoint the transaction. `amountDistribution` and `digestSignatures` are critical for the financial and security aspects of the identification process, each being a typed collection.">
|
||||
public function __construct(
|
||||
public string $transactionId,
|
||||
@@ -31,7 +35,7 @@ class IdentifyRequestData extends Data
|
||||
#[DataCollectionOf(AmountDistributionData::class)]
|
||||
public DataCollection $amountDistribution,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the response of an identification request.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for payer details.">
|
||||
|
||||
@@ -14,22 +14,26 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO requires `DigestSignatureData` as a collection, so `DataCollectionOf` is used.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for refunding an unidentified transaction.">
|
||||
// <BELIEF_STATE "This DTO ensures the refund request payload is strictly typed and complete, preventing malformed requests for a critical financial operation.">
|
||||
// <BELIEF_STATE "This DTO encapsulates the necessary information to process a refund for a transaction that could not be identified or processed normally.">
|
||||
class RefundUnidentifiedTransactionRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the refund request.">
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define the properties of the refund request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory fields: `accountNumber` and `id` to target the transaction, and `digestSignatures` for security and authorization.">
|
||||
public function __construct(
|
||||
public string $accountNumber,
|
||||
public string $id,
|
||||
public string $id, // Transaction ID
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the response of a refund request.">
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a collection of unidentified transactions.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Identification;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for an unidentified transaction.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a nominal account.">
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
use Apoloz\AlfaBank\Data\Shared\PayerInfoData;
|
||||
use Apoloz\AlfaBank\Data\Shared\RecipientInfoData;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for detailed payoff records.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for cryptographic digest signatures.">
|
||||
|
||||
@@ -13,15 +13,23 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components for complex nested data structures.">
|
||||
// <BELIEF_STATE "This DTO orchestrates several other DTOs and collections (`RecipientData`, `PayoutRequisiteData`, `DigestSignatureData`), requiring `DataCollectionOf` for typed arrays.">
|
||||
use Apoloz\AlfaBank\Data\Common\PayoutRequisiteData;
|
||||
use Apoloz\AlfaBank\Data\Common\RecipientData;
|
||||
use Apoloz\AlfaBank\Data\Common\TaxData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for creating a new payoff request.">
|
||||
// <BELIEF_STATE "This DTO provides a robust contract for the complex payload required to initiate a payment. It ensures that all financial and security-critical data is strictly typed and correctly structured.">
|
||||
class PayoffRequestData extends Data
|
||||
{
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the payoff request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory fields like `type`, `accountNumber`, `recipient`, `payoutRequisite`, `amount`, `purpose`, and `digestSignatures`. Nested DTOs (`RecipientData`, `PayoutRequisiteData`) ensure detailed sub-structures are also type-safe. Optional fields like `deponent`, `requisites`, and `tax` provide flexibility for different payment scenarios.">
|
||||
public function __construct(
|
||||
@@ -29,13 +37,15 @@ class PayoffRequestData extends Data
|
||||
public string $accountNumber,
|
||||
public RecipientData $recipient,
|
||||
public PayoutRequisiteData $payoutRequisite,
|
||||
#[Numeric]
|
||||
public string $amount,
|
||||
public string $purpose,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $deponent = null,
|
||||
public ?array $requisites = null, // Can be nested DTOs if defined
|
||||
public ?array $tax = null, // Can be nested DTOs if defined
|
||||
#[DataCollectionOf(PayoutRequisiteData::class)]
|
||||
public ?DataCollection $requisites = null,
|
||||
public ?TaxData $tax = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the response of a payoff request.">
|
||||
@@ -28,4 +29,4 @@ class PayoffResponseData extends Data
|
||||
public ?string $description = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for payout requisite details.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for recipient details.">
|
||||
|
||||
@@ -13,16 +13,19 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import necessary classes for cryptographic signatures, data collection, and Laravel Data.">
|
||||
// <BELIEF_STATE "These imports are essential for constructing a secure and structured tax refund request. `DigestSignatureData` ensures the integrity and authenticity of the request, `DataCollectionOf` and `DataCollection` facilitate handling multiple signatures, and `Data` provides the foundational capabilities for data object mapping and validation.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the tax refund request data structure.">
|
||||
// <BELIEF_STATE "The `TaxRefundRequestData` DTO encapsulates all the necessary information to process a tax refund. It includes critical financial and beneficiary identifiers, along with a mechanism to handle remaining funds and mandatory cryptographic signatures to ensure request security and non-repudiation.">
|
||||
class TaxRefundRequestData extends Data
|
||||
{
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define the properties of the tax refund request DTO.">
|
||||
// <BELIEF_STATE "Properties are defined directly in the constructor, leveraging PHP 8's property promotion for conciseness. Mandatory fields like `beneficiaryId`, `amount`, `purpose`, and `digestSignatures` ensure a complete request. `keepOnNominalAccount` provides control over fund disposition, and `requisiteId` allows for specific payout instructions.">
|
||||
public function __construct(
|
||||
@@ -31,7 +34,7 @@ class TaxRefundRequestData extends Data
|
||||
public string $purpose,
|
||||
public bool $keepOnNominalAccount,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $requisiteId = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\Payouts;
|
||||
// <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 tax refund response payloads.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the tax refund response data structure.">
|
||||
|
||||
@@ -14,22 +14,33 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\UniversalRecipients;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO requires `DigestSignatureData` as a collection, so `DataCollectionOf` is used.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for creating a new universal recipient.">
|
||||
// <BELIEF_STATE "This DTO ensures the request payload is strictly typed and complete for creating a universal recipient.">
|
||||
// <SECTION name="class_definition" intent="Define a DTO for creating a universal recipient.">
|
||||
// <BELIEF_STATE "This DTO handles the data for universal recipients, which are a specialized type of counterparty. It ensures all necessary identification and banking details are provided.">
|
||||
class CreateUniversalRecipientRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the universal recipient creation request.">
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define the properties of the universal recipient creation request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory fields: `accountNumber` and `type` to identify the recipient and its context, and `digestSignatures` for security and authorization.">
|
||||
public function __construct(
|
||||
public string $accountNumber,
|
||||
public string $type,
|
||||
#[DataCollectionOf(DigestSignatureData::class)]
|
||||
public DataCollection $digestSignatures,
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
public ?string $inn = null,
|
||||
public ?string $kpp = null,
|
||||
public ?string $ogrn = null,
|
||||
public ?string $name = null,
|
||||
public ?string $bankName = null,
|
||||
public ?string $bankBic = null,
|
||||
public ?string $bankCorrespondentAccount = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\UniversalRecipients;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a single universal recipient's details.">
|
||||
|
||||
@@ -14,7 +14,8 @@ namespace Apoloz\AlfaBank\Data\NominalAccounts\UniversalRecipients;
|
||||
// <SECTION name="module_imports" intent="Import the base response DTO.">
|
||||
// <BELIEF_STATE "This DTO reuses `CreateBeneficiaryResponseData` as its base, indicating that the response structure for creating universal recipients is identical to that for beneficiaries. This promotes consistency and reduces code duplication.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryResponseData;
|
||||
use Spatie\LaravelData\Data; // Included for clarity, though inherited from base DTO.
|
||||
|
||||
// Included for clarity, though inherited from base DTO.
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for the response of creating a universal recipient.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for pagination metadata.">
|
||||
|
||||
@@ -17,59 +17,59 @@ class PaymentData extends Data
|
||||
public function __construct(
|
||||
#[MapOutputName('Amount')]
|
||||
public float $amount,
|
||||
|
||||
|
||||
#[MapOutputName('DocDate')]
|
||||
public string $date,
|
||||
|
||||
|
||||
#[MapOutputName('DocNo')]
|
||||
public string $externalId,
|
||||
|
||||
|
||||
#[MapOutputName('OperationCode')]
|
||||
public string $operationCode,
|
||||
|
||||
|
||||
#[MapOutputName('PayeeAccount')]
|
||||
public string $payeeAccount,
|
||||
|
||||
|
||||
#[MapOutputName('PayeeBankBic'), Numeric, Size(9)]
|
||||
public string $payeeBankBic,
|
||||
|
||||
|
||||
#[MapOutputName('PayeeName')]
|
||||
public string $payeeName,
|
||||
|
||||
|
||||
#[MapOutputName('PayerAccount')]
|
||||
public string $payerAccount,
|
||||
|
||||
|
||||
#[MapOutputName('PayerBankBic'), Numeric, Size(9)]
|
||||
public string $payerBankBic,
|
||||
|
||||
|
||||
#[MapOutputName('PayerBankCorrAccount')]
|
||||
public string $payerBankCorrAccount,
|
||||
|
||||
|
||||
#[MapOutputName('PayerInn'), Numeric]
|
||||
public string $payerInn,
|
||||
|
||||
|
||||
#[MapOutputName('PayerName')]
|
||||
public string $payerName,
|
||||
|
||||
|
||||
#[MapOutputName('Priority')]
|
||||
public string $priority,
|
||||
|
||||
|
||||
#[MapOutputName('Purpose')]
|
||||
public string $purpose,
|
||||
|
||||
|
||||
#[MapOutputName('Number')]
|
||||
public ?string $number = null,
|
||||
|
||||
|
||||
#[MapOutputName('PayeeKpp')]
|
||||
public ?string $payeeKpp = null,
|
||||
|
||||
|
||||
#[MapOutputName('PayeeBankCorrAccount')]
|
||||
public ?string $payeeBankCorrAccount = null,
|
||||
|
||||
|
||||
#[MapOutputName('PaymentType')]
|
||||
public ?string $paymentType = null,
|
||||
|
||||
|
||||
#[MapOutputName('DigestSignatures'), DataCollectionOf(DigestSignatureData::class)]
|
||||
public ?DataCollection $digestSignatures = null,
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,19 @@ namespace Apoloz\AlfaBank\Data\Sandbox\Payments;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO can include `DigestSignatureData` as a collection, so `DataCollectionOf` is used for type safety.">
|
||||
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
||||
use Apoloz\AlfaBank\Traits\SignableData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for creating a sandbox RUB payment.">
|
||||
// <BELIEF_STATE "This DTO provides a robust contract for simulating payments, ensuring all test payment data is strictly typed and correctly structured.">
|
||||
class CreateRubPaymentRequestData extends Data
|
||||
{
|
||||
use SignableData;
|
||||
|
||||
// <SECTION name="constructor_properties" intent="Define all properties for the test RUB payment request.">
|
||||
// <BELIEF_STATE "The constructor includes comprehensive payment details covering `externalId`, `amount`, `date`, `number`, `operationCode`, full payer/payee information (`payeeAccount`, `payeeBankBic`, etc.), `purpose`, and default values for `priority` and `deliveryKind`. Optional fields like `digestSignatures` and departmental/VAT info provide flexibility for detailed testing.">
|
||||
public function __construct(
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Apoloz\AlfaBank\Data\Shared;
|
||||
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||
use Spatie\LaravelData\Attributes\Validation\Size;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for payer information.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\Statement;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for monetary amount data.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\Statement;
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from JSON and provides other DTO benefits.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for Russian Ruble transfer details.">
|
||||
|
||||
@@ -17,6 +17,7 @@ use Apoloz\AlfaBank\Data\PaginationData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a collection of account transactions.">
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Data\Statement;
|
||||
// <SECTION name="module_imports" intent="Import necessary DTOs and Spatie Laravel Data components.">
|
||||
// <BELIEF_STATE "This DTO includes nested `AmountData` for monetary values and `RurTransferData` for detailed transfer information.">
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a DTO for a single account transaction.">
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Apoloz\AlfaBank\Data;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
class WebhookCollectionData extends Data
|
||||
|
||||
@@ -20,4 +20,4 @@ class BaasWebhookData extends BaseWebhookData
|
||||
organizationId: $organizationId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ class BaseWebhookData extends Data
|
||||
public ?string $eventTime = null,
|
||||
public ?string $organizationId = null,
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,4 @@ class SbpWebhookData extends BaseWebhookData
|
||||
organizationId: $organizationId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
// </SECTION>
|
||||
|
||||
case ID_PRODUCTION = 'https://id.alfabank.ru';
|
||||
case ID_SANDBOX = 'https://id-sandbox.alfabank.ru';
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Apoloz\AlfaBank\Exceptions;
|
||||
// <SECTION name="module_imports" intent="Import the base Saloon Response class.">
|
||||
// <BELIEF_STATE "The custom exception needs to hold the original Saloon `Response` object to give developers full access to the raw HTTP response for debugging purposes.">
|
||||
use Saloon\Http\Response;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define a custom exception that extends PHP's base Exception class.">
|
||||
|
||||
12
src/Exceptions/SignerException.php
Normal file
12
src/Exceptions/SignerException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class SignerException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
// MODULE_CONTRACT: Provides a Saloon middleware to standardize error handling for Alfa Bank API responses.
|
||||
// RESPONSIBILITY: To intercept API responses, identify various error formats (BaaS, SBP, or generic HTTP), and throw a consistent `AlfaBankException`, thereby centralizing error reporting and simplifying error management in the SDK.
|
||||
@@ -13,6 +14,7 @@ namespace Apoloz\AlfaBank\Http\Middleware;
|
||||
// <BELIEF_STATE "These imports are crucial for the middleware's functionality. `AlfaBankException` provides a standardized way to signal API errors within the SDK, while `Response` from Saloon is the object that the middleware processes.">
|
||||
use Apoloz\AlfaBank\Exceptions\AlfaBankException;
|
||||
use Saloon\Http\Response;
|
||||
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the Alfa Bank API response error handling middleware.">
|
||||
@@ -24,7 +26,7 @@ class AlfaResponseMiddleware
|
||||
public function __invoke(Response $response): void
|
||||
{
|
||||
$contentType = $response->header('Content-Type') ?? '';
|
||||
|
||||
|
||||
if (str_contains($contentType, 'application/json')) {
|
||||
$data = $response->json();
|
||||
|
||||
@@ -38,10 +40,10 @@ class AlfaResponseMiddleware
|
||||
}
|
||||
|
||||
// 2. Handle SBP specific errors
|
||||
if (isset($data['ErrorCode']) && (string)$data['ErrorCode'] !== '0') {
|
||||
if (isset($data['ErrorCode']) && (string) $data['ErrorCode'] !== '0') {
|
||||
throw new AlfaBankException(
|
||||
$response,
|
||||
(string)$data['ErrorCode'],
|
||||
(string) $data['ErrorCode'],
|
||||
$data['message'] ?? 'No message'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ class LoggingMiddleware implements RequestMiddleware, ResponseMiddleware
|
||||
if (isset($headers['Authorization'])) {
|
||||
$headers['Authorization'] = 'Bearer *****';
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
// </SECTION>
|
||||
@@ -53,7 +54,9 @@ class LoggingMiddleware implements RequestMiddleware, ResponseMiddleware
|
||||
// <BELIEF_STATE "This protected recursive helper method inspects the request or response body for predefined sensitive keys (e.g., `pan`, `client_secret`). If a sensitive key is found, its value is replaced with a mask. The method recursively processes nested arrays, ensuring that sensitive data is masked even within complex JSON structures, thereby enhancing log security.">
|
||||
protected function maskBody(mixed $body): mixed
|
||||
{
|
||||
if (!is_array($body)) return $body;
|
||||
if (! is_array($body)) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$sensitiveKeys = ['pan', 'cvc', 'digestSignatures', 'client_secret', 'refresh_token', 'access_token'];
|
||||
|
||||
@@ -64,6 +67,7 @@ class LoggingMiddleware implements RequestMiddleware, ResponseMiddleware
|
||||
$body[$key] = $this->maskBody($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user