forked from apoloz/alfa-bank-sdk
557 lines
14 KiB
Markdown
557 lines
14 KiB
Markdown
# Alfa Bank SDK (Unofficial)
|
|
|
|
This is an unofficial PHP SDK for the [Alfa Bank API](https://developers.alfabank.ru/), built using [Saloon](https://docs.saloon.dev) and [Spatie Laravel Data](https://spatie.be/docs/laravel-data).
|
|
|
|
## Requirements
|
|
|
|
- PHP 8.2+
|
|
- OpenSSL (for certificate generation/handling)
|
|
- [Saloon](https://docs.saloon.dev) v3.0+
|
|
- [Spatie Laravel Data](https://spatie.be/docs/laravel-data)
|
|
- [phpseclib](https://phpseclib.org/) (for Card Encryption)
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
composer require apoloz/alfa-bank-sdk
|
|
```
|
|
|
|
## Security & Setup (Crucial)
|
|
|
|
Alfa Bank uses a comprehensive security model requiring **two distinct sets of keys**:
|
|
1. **Transport (mTLS):** To establish the SSL connection.
|
|
2. **Signing (JWS/CMS):** To digitally sign the request body.
|
|
|
|
### Where to get keys?
|
|
- **Sandbox:** Provided by the bank support or downloaded from the Developer Portal (usually as `.pfx` or `.zip`).
|
|
- **Production:**
|
|
- **Transport (mTLS):** Generate a private key and CSR using OpenSSL (`openssl genrsa`, `openssl req`). Send the CSR to `alfa_api@alfabank.ru`. The bank will reply with your Certificate.
|
|
- **Signing:** Generated by you (the client). The public certificate must be registered in "Alfa Business Online" or sent to your onboarding manager.
|
|
|
|
### 1. Converting PFX to PEM
|
|
If the bank provided a `.pfx` file for signing, you must convert it to PEM format:
|
|
```bash
|
|
# Extract Private Key
|
|
openssl pkcs12 -in signing.pfx -nocerts -out signing_key.pem -nodes
|
|
|
|
# Extract Public Certificate
|
|
openssl pkcs12 -in signing.pfx -clcerts -nokeys -out signing_cert.pem
|
|
```
|
|
|
|
### 2. Initialization
|
|
You can now initialize the SDK with both key pairs:
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\AlfaBank;
|
|
use Apoloz\AlfaBank\Enums\Environment;
|
|
|
|
$baas = AlfaBank::baas(
|
|
// Transport Keys (mTLS)
|
|
certificatePath: '/path/to/transport_cert.pem',
|
|
privateKeyPath: '/path/to/transport_key.pem',
|
|
|
|
// Signing Keys (Business Logic) - Optional, defaults to transport keys if not set
|
|
signingCertificatePath: '/path/to/signing_cert.pem',
|
|
signingPrivateKeyPath: '/path/to/signing_key.pem',
|
|
|
|
environment: Environment::SANDBOX,
|
|
caBundlePath: '/path/to/root_ca_bundle.crt'
|
|
);
|
|
```
|
|
|
|
## Core Usage
|
|
|
|
### Authentication (BaaS)
|
|
|
|
Use the `login` method to perform OAuth Client Credentials flow:
|
|
|
|
```php
|
|
$baas->login(
|
|
clientId: 'your-client-id',
|
|
clientSecret: 'your-client-secret'
|
|
);
|
|
```
|
|
|
|
### Making Requests
|
|
|
|
#### SBP: Generate QR Code
|
|
```php
|
|
use Apoloz\AlfaBank\Sbp\Requests\GetQrCodeRequest;
|
|
use Apoloz\AlfaBank\Sbp\Data\QrCodeRequestData;
|
|
|
|
$sbp = AlfaBank::sbp(
|
|
certificatePath: '...',
|
|
privateKeyPath: '...',
|
|
keyNameAlias: '9728109955', // Terminal ID
|
|
signingPrivateKeyPath: '/path/to/signing_key.pem' // Specific signing key for SBP body
|
|
);
|
|
|
|
$response = $sbp->send(new GetQrCodeRequest(
|
|
new QrCodeRequestData(TermNo: '...', amount: '100')
|
|
));
|
|
|
|
$link = $response->dto()->payload;
|
|
```
|
|
|
|
#### Nominal Accounts
|
|
```php
|
|
$response = $baas->send(new CreateBeneficiaryRequest($payload));
|
|
```
|
|
|
|
### Self-Employed (Payouts & Status)
|
|
|
|
The SDK supports managing self-employed beneficiaries, checking their FTS status, and processing payouts.
|
|
|
|
#### Check Taxpayer Status
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\SelfEmployed\GetTaxpayerStatusRequest;
|
|
use Apoloz\AlfaBank\Data\SelfEmployed\TaxpayerStatusRequestData;
|
|
|
|
$payload = new TaxpayerStatusRequestData(
|
|
externalId: 'unique-id',
|
|
selfemployedVatinList: ['123456789012']
|
|
);
|
|
|
|
$response = $baas->send(new GetTaxpayerStatusRequest($payload));
|
|
$status = $response->dto()->taxpayerStatusList[0];
|
|
```
|
|
|
|
#### Process Payout
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\SelfEmployed\CreatePayoutRequest;
|
|
use Apoloz\AlfaBank\Data\SelfEmployed\PayoutRequestData;
|
|
|
|
$payload = new PayoutRequestData(
|
|
externalId: 'ext-id-456',
|
|
company: ['vatin' => '7700000000'],
|
|
requisite: [
|
|
['amount' => 1000, 'vatin' => '112233445566']
|
|
]
|
|
);
|
|
$response = $baas->send(new CreatePayoutRequest($payload));
|
|
```
|
|
|
|
### File Exchange
|
|
|
|
Upload and download files securely.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\FileExchange\UploadFileRequest;
|
|
use Apoloz\AlfaBank\Requests\FileExchange\DownloadFileRequest;
|
|
|
|
// Upload
|
|
$stream = fopen('path/to/file', 'r');
|
|
$baas->send(new UploadFileRequest($uploadUrl, $stream, $fileSize));
|
|
|
|
// Download
|
|
$response = $baas->send(new DownloadFileRequest($downloadUrl));
|
|
$content = $response->body();
|
|
```
|
|
|
|
### Payments (RUR Payment Orders)
|
|
|
|
Create standard payment orders and check their status.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Payments\CreatePaymentRequest;
|
|
use Apoloz\AlfaBank\Data\Payments\PaymentData;
|
|
|
|
$payload = new PaymentData(
|
|
amount: 100.50,
|
|
date: '2023-10-25',
|
|
externalId: 'order-12345',
|
|
operationCode: '01',
|
|
payeeAccount: '40702...',
|
|
payeeBankBic: '044525225',
|
|
payeeName: 'Partner LLC',
|
|
payerAccount: '40702...',
|
|
payerBankBic: '044525593',
|
|
payerBankCorrAccount: '30101...',
|
|
payerInn: '7700000000',
|
|
payerName: 'My Company',
|
|
priority: '5',
|
|
purpose: 'Payment for services'
|
|
);
|
|
|
|
// Create Payment
|
|
$baas->send(new CreatePaymentRequest($payload));
|
|
|
|
// Check State
|
|
use Apoloz\AlfaBank\Requests\Payments\GetPaymentStateRequest;
|
|
$state = $baas->send(new GetPaymentStateRequest('order-12345'))->dto();
|
|
echo $state->bankStatus; // e.g. ACCEPTED
|
|
```
|
|
|
|
### Cards
|
|
|
|
Retrieve a list of client cards.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Cards\GetCardsRequest;
|
|
|
|
$response = $baas->send(new GetCardsRequest());
|
|
foreach ($response->dto() as $card) {
|
|
echo $card->maskedNumber . ' - ' . $card->status;
|
|
}
|
|
```
|
|
|
|
### B2C Payouts (SBP via Phone)
|
|
|
|
Send money to individuals using their phone number through SBP.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\B2C\CreateB2CPaymentRequest;
|
|
use Apoloz\AlfaBank\Data\B2C\B2CPaymentRequestData;
|
|
use Apoloz\AlfaBank\Data\B2C\B2CClientData;
|
|
use Apoloz\AlfaBank\Data\B2C\B2CPaymentRecipientData;
|
|
|
|
$payload = new B2CPaymentRequestData(
|
|
client: new B2CClientData(b2cClientId: '...', partnerId: 123),
|
|
inn: '7700000000',
|
|
account: '40702...',
|
|
total: 100.53,
|
|
signedBody: '...base64_signature...',
|
|
payments: B2CPaymentRecipientData::collect([
|
|
[
|
|
'phone' => '79001112233',
|
|
'amount' => 100.53,
|
|
'nspkBankId' => '100000000001',
|
|
'sequence' => 'unique-seq-1',
|
|
'name' => ['first' => 'Ivan', 'last' => 'Ivanov']
|
|
]
|
|
], DataCollection::class)
|
|
);
|
|
|
|
$baas->send(new CreateB2CPaymentRequest($payload));
|
|
```
|
|
|
|
### Payment Registries (Batch Ruble Payments)
|
|
|
|
Create a registry for multiple ruble payments and sign it.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Registries\CreateRegistryRequest;
|
|
use Apoloz\AlfaBank\Data\Registries\RegistryRequestData;
|
|
use Apoloz\AlfaBank\Data\Registries\RegistryPaymentData;
|
|
|
|
$payload = new RegistryRequestData(
|
|
payments: RegistryPaymentData::collect([
|
|
[
|
|
'amount' => 1000.0,
|
|
'date' => '2023-11-01',
|
|
'payeeAccount' => '40702...',
|
|
'payeeBankBic' => '044525225',
|
|
'payeeInn' => '7700000000',
|
|
'payeeName' => 'Vendor LLC',
|
|
'payerAccount' => '40702...',
|
|
'purpose' => 'Batch payment part 1'
|
|
]
|
|
], DataCollection::class),
|
|
description: 'Weekly payroll'
|
|
);
|
|
|
|
$response = $baas->send(new CreateRegistryRequest($payload));
|
|
$registryId = $response->json('id');
|
|
|
|
// Sign Registry
|
|
use Apoloz\AlfaBank\Requests\Registries\SignRegistryRequest;
|
|
use Apoloz\AlfaBank\Data\Registries\RegistrySignatureData;
|
|
use Spatie\LaravelData\DataCollection;
|
|
|
|
$signatures = RegistrySignatureData::collect([
|
|
[
|
|
'base64Encoded' => '...', // Base64 signature
|
|
'signatureType' => 'RSA',
|
|
'certificateUuid' => '...'
|
|
]
|
|
], DataCollection::class);
|
|
|
|
$baas->send(new SignRegistryRequest($registryId, $signatures));
|
|
```
|
|
|
|
### Customer Info
|
|
|
|
Get organization profile.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\CustomerInfo\GetCustomerInfoRequest;
|
|
|
|
$info = $baas->send(new GetCustomerInfoRequest())->dto();
|
|
echo $info->fullName;
|
|
```
|
|
|
|
### Limits
|
|
|
|
Get client's operation limits.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Limits\GetGeneralLimitsRequest;
|
|
|
|
$response = $baas->send(new GetGeneralLimitsRequest());
|
|
$limits = $response->json();
|
|
```
|
|
|
|
### Debit Cards
|
|
|
|
Apply for a new debit card.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\DebitCards\CreateDebitCardApplicationRequest;
|
|
use Apoloz\AlfaBank\Data\DebitCards\DebitCardApplicationData;
|
|
|
|
$payload = new DebitCardApplicationData(
|
|
embossingName: 'IVAN IVANOV',
|
|
firstName: 'Иван',
|
|
lastName: 'Иванов',
|
|
middleName: 'Иванович',
|
|
mobilePhone: '79001112233',
|
|
passportBirthDate: '1990-01-01',
|
|
sex: 'm',
|
|
sopdConfirmed: true
|
|
);
|
|
|
|
$baas->send(new CreateDebitCardApplicationRequest($payload));
|
|
```
|
|
|
|
### User Compliance
|
|
|
|
Check user compliance for loans.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Compliance\GetUserComplianceRequest;
|
|
|
|
$baas->send(new GetUserComplianceRequest());
|
|
```
|
|
|
|
### DFA (Digital Financial Assets)
|
|
|
|
List initial placements.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\DFA\GetDfaInitialPlacementListRequest;
|
|
|
|
$baas->send(new GetDfaInitialPlacementListRequest(limit: 5));
|
|
```
|
|
|
|
### Currency Rates
|
|
|
|
Get currency rates by office.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Rates\GetOfficeRatesRequest;
|
|
|
|
$baas->send(new GetOfficeRatesRequest());
|
|
```
|
|
|
|
### Escrow Accounts
|
|
|
|
Manage construction projects and escrow orders.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Escrow\GetBuildProjectsRequest;
|
|
|
|
$projects = $baas->send(new GetBuildProjectsRequest())->json();
|
|
```
|
|
|
|
### Power of Attorney (Poa)
|
|
|
|
Request and check status of digital POAs.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Poa\CreatePoaRequest;
|
|
|
|
$response = $baas->send(new CreatePoaRequest('poa-123', '7700000000'));
|
|
$uuid = $response->json('requestUuid');
|
|
```
|
|
|
|
### Loyalty
|
|
|
|
Access bonus accounts and transaction history.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Loyalty\GetLoyaltyAccountsRequest;
|
|
|
|
$accounts = $baas->send(new GetLoyaltyAccountsRequest())->json();
|
|
```
|
|
|
|
### Reports
|
|
|
|
Order certificates and reports.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Reports\CreateAccountBalanceReportRequest;
|
|
|
|
$baas->send(new CreateAccountBalanceReportRequest(['40702...'], 'eng'));
|
|
```
|
|
|
|
### Crypto Service
|
|
|
|
Manage encryption and signing certificates.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Crypto\GetCertificatesRequest;
|
|
|
|
$certificates = $baas->send(new GetCertificatesRequest())->json();
|
|
```
|
|
|
|
### Partner (MMB)
|
|
|
|
Access partner program data and applications.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Partner\GetPartnerApplicationsRequest;
|
|
|
|
$apps = $baas->send(new GetPartnerApplicationsRequest())->json();
|
|
```
|
|
|
|
### Miscellaneous
|
|
|
|
- **Bonus Numbers**: `$baas->send(new GetBonusNumbersRequest());`
|
|
- **Credit Offers**: `$baas->send(new GetCreditOffersRequest());`
|
|
- **Self-encashment**: `$baas->send(new CreateSelfEncashmentRegistryRequest($data));`
|
|
- **Business Lounge**: `$baas->send(new GetLoungeBalanceRequest());`
|
|
- **Scoring**: `$baas->send(new GetComplianceScoreRequest('7700000000'));`
|
|
- **Security**: `$baas->send(new GetSubscriptionsRequest());`
|
|
- **Logistics**: `$baas->send(new GetDeliveriesRequest('...'));`
|
|
- **Webhooks**: `$baas->send(new GetWebhooksRequest());`
|
|
- **1C Integration**: `$baas->send(new CheckMerchantRequest('MF123', 'Basic ...'));`
|
|
- **Claims**: `$baas->send(new ResolveClaimRequest('ID', $data));`
|
|
- **Orders (Travel/Afisha)**: `$baas->send(new CreateTravelOrderRequest($data));`
|
|
- **Salary Project**: `$baas->send(new CreatePayrollApplicationRequest($payload));`
|
|
- **Cloud Signature**: `$baas->send(new SubmitSigningRequest('REQ-ID'));`
|
|
- **Aff Integration**: `$baas->send(new CheckComplianceRequest('INN', 'Name'));`
|
|
- **Digital Ruble**: `$sbp->send(new CreateUniQrRequest($uniQrData));`
|
|
|
|
### Signing Data (CMS)
|
|
|
|
For requests requiring a digital signature (e.g., Payments), you must canonicalize and sign the data manually.
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Utils\CmsSigner;
|
|
use Apoloz\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
|
|
use Spatie\LaravelData\DataCollection;
|
|
|
|
// 1. Prepare data
|
|
$fields = [
|
|
'amount' => 100.50,
|
|
// ... other fields
|
|
];
|
|
|
|
// 2. Canonicalize & Sign
|
|
$json = CmsSigner::canonicalize($fields);
|
|
$cert = file_get_contents('/path/to/signing_cert.pem');
|
|
$key = file_get_contents('/path/to/signing_key.pem');
|
|
$signature = CmsSigner::sign($json, $cert, $key);
|
|
|
|
// 3. Attach to DTO
|
|
$paymentData->digestSignatures = DigestSignatureData::collect([
|
|
[
|
|
'base64Encoded' => $signature,
|
|
'signatureType' => 'CMS'
|
|
]
|
|
], DataCollection::class);
|
|
```
|
|
|
|
## Advanced Features
|
|
|
|
### Pagination
|
|
The SDK supports automatic pagination for list endpoints:
|
|
|
|
```php
|
|
use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest;
|
|
|
|
$paginator = $baas->paginate(new GetAccountsRequest());
|
|
|
|
foreach ($paginator as $account) {
|
|
// Each item is processed automatically
|
|
}
|
|
```
|
|
|
|
## Laravel Integration
|
|
|
|
The package supports auto-discovery. Publish the config file:
|
|
|
|
```bash
|
|
php artisan vendor:publish --tag=alfa-bank-config
|
|
```
|
|
|
|
Configure your `.env`:
|
|
```env
|
|
ALFA_BAAS_CERT_PATH=/path/to/transport_cert.pem
|
|
# ... other settings
|
|
ALFA_WEBHOOK_USER=your_webhook_user
|
|
ALFA_WEBHOOK_PASSWORD=your_webhook_password
|
|
```
|
|
|
|
### Webhook Handling (Laravel)
|
|
|
|
The SDK provides a built-in controller and route macro to handle incoming webhooks and dispatch Laravel events.
|
|
|
|
1. **Register the route** in `routes/api.php`:
|
|
```php
|
|
Route::alfaWebhooks('/webhooks/alfa');
|
|
```
|
|
|
|
2. **Listen for the event** in `app/Providers/EventServiceProvider.php`:
|
|
```php
|
|
use Apoloz\AlfaBank\Laravel\Events\AlfaWebhookReceived;
|
|
|
|
protected $listen = [
|
|
AlfaWebhookReceived::class => [
|
|
\App\Listeners\HandleAlfaWebhook::class,
|
|
],
|
|
];
|
|
```
|
|
|
|
3. **Handle the DTO** in your listener:
|
|
```php
|
|
public function handle(AlfaWebhookReceived $event)
|
|
{
|
|
$webhook = $event->webhook; // BaseWebhookData DTO
|
|
|
|
if ($webhook->object === 'ul_transaction_default') {
|
|
$transaction = $webhook->data;
|
|
// ...
|
|
}
|
|
}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
The SDK throws `Apoloz\AlfaBank\Exceptions\AlfaBankException` when the API returns an error.
|
|
|
|
```php
|
|
try {
|
|
$baas->send($request);
|
|
} catch (\Apoloz\AlfaBank\Exceptions\AlfaBankException $e) {
|
|
echo $e->getErrorCode(); // e.g. "invalid_token" or "RQ05014"
|
|
echo $e->getMessage();
|
|
}
|
|
```
|
|
|
|
## Utilities
|
|
|
|
- **Card PAN Encryption**: `CardEncryptor::encrypt($pan, $pubKey)`
|
|
- **CMS Signing**: `CmsSigner::sign($data, $cert, $key)`
|
|
- **JWS/JWE**: `JwsSigner` and `JweEncryptor` for transport security.
|
|
|
|
## Diagnostics
|
|
|
|
### Health Check (Laravel)
|
|
Run the built-in Artisan command to verify your mTLS certificates and connection:
|
|
|
|
```bash
|
|
php artisan alfa:check
|
|
```
|
|
|
|
### Logging
|
|
Attach a PSR-3 logger to debug requests (sensitive data redacted):
|
|
|
|
```php
|
|
$baas->withLogging(Log::getLogger());
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|