first commit

This commit is contained in:
2026-01-14 16:40:33 +03:00
commit f803480f7b
184 changed files with 5573 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/vendor/
/.phpunit.cache/
/node_modules/
.env
phpunit.xml
composer.lock

145
README.md Normal file
View File

@@ -0,0 +1,145 @@
# 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)
- [Spatie Laravel Data](https://spatie.be/docs/laravel-data)
- [phpseclib](https://phpseclib.org/) (for Card Encryption)
## Installation
```bash
composer require gemini-cli/alfa-bank-sdk
```
## 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/cert.cer
ALFA_BAAS_KEY_PATH=/path/to/key.key
ALFA_BAAS_ENV=sandbox
ALFA_SBP_CERT_PATH=/path/to/cert.cer
ALFA_SBP_KEY_PATH=/path/to/key.key
ALFA_SBP_KEY_ALIAS=your-alias
ALFA_SBP_ENV=sandbox
```
Now you can inject the connectors:
```php
use Gemini\AlfaBank\AlfaBankConnector;
public function index(AlfaBankConnector $connector) {
$connector->withToken('...')->send(new GetNominalAccountsRequest());
}
```
## Core Usage (Generic PHP)
### 1. Initialize the Connector
```php
use Gemini\AlfaBank\AlfaBankConnector;
use Gemini\AlfaBank\Enums\Environment;
$connector = new AlfaBankConnector(
certificatePath: '/path/to/client.cer',
privateKeyPath: '/path/to/private.key',
environment: Environment::SANDBOX
);
```
### 2. Authentication
```php
use Gemini\AlfaBank\Requests\Auth\GetTokenRequest;
$request = new GetTokenRequest(
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
code: 'auth-code',
redirectUri: 'https://your-app.com/callback'
);
$response = $connector->send($request);
$tokenData = $response->dto();
$connector->withToken($tokenData->access_token);
```
### 3. SBP / QR Payouts (C2B)
```php
use Gemini\AlfaBank\Sbp\SbpConnector;
$sbpConnector = new SbpConnector(
certificatePath: '/path/to/client.cer',
privateKeyPath: '/path/to/private.key',
keyNameAlias: 'your-key-alias',
environment: Environment::SANDBOX
);
$response = $sbpConnector->send(new GetQrCodeRequest($payload));
```
### 4. Utilities
#### Card PAN Encryption
Required for card payouts in Nominal Accounts.
```php
use Gemini\AlfaBank\Utils\CardEncryptor;
$encryptedPan = CardEncryptor::encrypt(
pan: '4779642610000010',
publicKeyPem: file_get_contents('alfa-card-key.pub')
);
```
#### JSON Web Signature (JWS)
Used for signing some BaaS requests.
```php
use Gemini\AlfaBank\Utils\JwsSigner;
$jws = JwsSigner::sign(
payload: ['foo' => 'bar'],
privateKeyPem: file_get_contents('my-key.pem'),
certificateSerialNumber: '610003...'
);
```
#### JSON Web Encryption (JWE)
Used for encrypting sensitive BaaS requests.
```php
use Gemini\AlfaBank\Utils\JweEncryptor;
$jwe = JweEncryptor::encrypt(
jws: $jws,
publicKeyPem: file_get_contents('alfa-cert.cer'),
certificateSerialNumber: '610003...'
);
```
## Features
- **Mutual TLS**: Automated certificate handling.
- **Laravel Ready**: Service Provider and Config included.
- **Typed DTOs**: Comprehensive response parsing.
- **Full Product Coverage**: BaaS (Nominal Accounts, Escrow), SBP (QR, Subscriptions), Digital Ruble.
## License
MIT

41
composer.json Normal file
View File

@@ -0,0 +1,41 @@
{
"name": "gemini-cli/alfa-bank-sdk",
"description": "Unofficial Alfa Bank API SDK for PHP using Saloon and Laravel Data",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Gemini CLI",
"email": "bot@example.com"
}
],
"require": {
"php": "^8.2",
"saloonphp/saloon": "^3.0",
"spatie/laravel-data": "^4.0",
"guzzlehttp/guzzle": "^7.0",
"phpseclib/phpseclib": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"pestphp/pest": "^2.0"
},
"autoload": {
"psr-4": {
"Gemini\\AlfaBank\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Gemini\\AlfaBank\\Laravel\\AlfaBankServiceProvider"
]
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"scripts": {
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage"
}
}

32
src/AlfaBankConnector.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace Gemini\AlfaBank;
use Gemini\AlfaBank\Enums\Environment;
use Saloon\Http\Connector;
use Saloon\Traits\Plugins\HasTimeout;
class AlfaBankConnector extends Connector
{
use HasTimeout;
public function __construct(
protected string $certificatePath,
protected string $privateKeyPath,
protected Environment $environment = Environment::PRODUCTION
) {}
public function resolveBaseUrl(): string
{
return $this->environment->value;
}
protected function defaultConfig(): array
{
return [
'cert' => $this->certificatePath,
'ssl_key' => $this->privateKeyPath,
'verify' => true, // Ensure SSL verification is on
];
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Gemini\AlfaBank\Data\Auth;
use Spatie\LaravelData\Data;
class TokenData extends Data
{
public function __construct(
public string $access_token,
public string $refresh_token,
public ?string $id_token,
public int $expires_in,
public string $token_type
) {}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Gemini\AlfaBank\Data\Auth\UserInfo;
use Gemini\AlfaBank\Data\PaginationData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class ClientConsentCollectionData extends Data
{
public function __construct(
public PaginationData $pagination,
#[DataCollectionOf(ClientConsentData::class)]
public DataCollection $items
) {}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gemini\AlfaBank\Data\Auth\UserInfo;
use Gemini\AlfaBank\Data\PaginationData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class ClientConsentData extends Data
{
public function __construct(
public string $createDateTime,
public string $sub,
public string $clientId,
public bool $isActive,
public string $expireDate,
public ?string $organizationIdHash = null,
) {}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gemini\AlfaBank\Data\Auth\UserInfo;
use Spatie\LaravelData\Data;
class UserInfoData extends Data
{
public function __construct(
public string $sub,
public ?string $iss = null,
public ?string $aud = null,
public ?string $name = null,
public ?string $given_name = null,
public ?string $family_name = null,
public ?string $middle_name = null,
public ?string $email = null,
public ?string $gender = null,
public ?string $birthdate = null,
public ?string $phone_number = null,
public ?string $inn = null,
// Add more claims as needed based on the response
) {}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Gemini\AlfaBank\Data\Crypto;
use Spatie\LaravelData\Data;
class CertificateData extends Data
{
public function __construct(
public string $id,
public string $clientId,
public string $ownerName,
public string $fingerprint,
public string $beginDateTime,
public string $expireDateTime,
public string $certificateBody,
public string $type,
public string $algorithm,
public string $serialNumber,
public ?string $organizationName = null,
public ?string $status = null,
) {}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Gemini\AlfaBank\Data\FileExchange;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class DownloadFileResponseData extends Data
{
public function __construct(
#[DataCollectionOf(FileLinkData::class)]
public DataCollection $downloadFileLinks,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\FileExchange;
use Spatie\LaravelData\Data;
class FileLinkData extends Data
{
public function __construct(
public string $url,
public string $method,
public ?FileLinkData $detachedSignatureLink = null,
) {}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Gemini\AlfaBank\Data\FileExchange;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class UploadFileResponseData extends Data
{
public function __construct(
#[DataCollectionOf(FileLinkData::class)]
public DataCollection $uploadFileLinks,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Spatie\LaravelData\Data;
class AddressData extends Data
{
public function __construct(
public string $type,
public string $address,
public ?string $country = null,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class BeneficiaryBalanceData extends Data
{
public function __construct(
public string $beneficiaryId,
public string $accountNumber,
public string $date,
#[DataCollectionOf(WalletBalanceData::class)]
public ?DataCollection $balances = null,
) {}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\PaginationData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class BeneficiaryTransactionCollectionData extends Data
{
public function __construct(
public ?PaginationData $pagination = null,
#[DataCollectionOf(BeneficiaryTransactionData::class)]
public ?DataCollection $transactionsList = null,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Spatie\LaravelData\Data;
class BeneficiaryTransactionData extends Data
{
public function __construct(
public string $type,
public string $amount,
public ?string $comment = null,
) {}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class CreateBeneficiaryRequestData extends Data
{
public function __construct(
public string $type,
public string $countryOfTaxResidence,
#[DataCollectionOf(AddressData::class)]
public DataCollection $addresses,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
public ?string $accountNumber = null,
public ?bool $taxResidentUS = null,
public ?bool $foreignTaxResident = null,
public ?bool $financialActivities = null,
public ?string $name = null,
public ?string $registrationDate = null,
public ?string $inn = null,
public ?string $kpp = null,
public ?string $ogrn = null,
public ?PersonalInformationData $personalInformation = null,
public ?string $phoneNumber = null,
public ?string $email = null,
#[DataCollectionOf(DocumentData::class)]
public ?DataCollection $documents = null,
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Spatie\LaravelData\Data;
class CreateBeneficiaryResponseData extends Data
{
public function __construct(
public string $requestId,
public string $status,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Spatie\LaravelData\Data;
class DocumentData extends Data
{
public function __construct(
public string $type,
public string $number,
public string $issueDate,
public string $organization,
public ?string $serial = null,
public ?string $expireDate = null,
public ?string $division = null,
) {}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Spatie\LaravelData\Data;
class PersonalInformationData extends Data
{
public function __construct(
public ?string $inn = null,
public ?string $firstName = null,
public ?string $middleName = null,
public ?string $lastName = null,
public ?string $citizenship = null,
public ?string $birthDate = null,
public ?string $birthPlace = null,
public ?string $snils = null,
) {}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class CreateRequisiteRequestData extends Data
{
public function __construct(
public string $type,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
public ?string $beneficiaryId = null,
public ?string $pan = null,
public ?string $phoneNumber = null,
public ?string $bankId = null,
public ?string $accountNumber = null,
public ?string $bik = null,
public ?string $name = null,
public ?string $inn = null,
public ?string $kpp = null,
public ?bool $isMain = null,
) {}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites;
use Spatie\LaravelData\Data;
class RequisiteData extends Data
{
public function __construct(
public ?string $id = null,
public string $type,
public bool $isMain,
public bool $isActive,
public ?string $pan = null,
public ?string $phoneNumber = null,
public ?string $bankId = null,
public ?string $accountNumber = null,
public ?string $bik = null,
public ?string $name = null,
public ?string $inn = null,
public ?string $kpp = null,
) {}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class UpdateRequisiteRequestData extends Data
{
public function __construct(
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
public ?string $pan = null,
public ?string $keyId = null,
public ?string $phoneNumber = null,
public ?string $bankId = null,
public ?string $accountNumber = null,
public ?string $bik = null,
public ?bool $isMain = null,
public ?bool $isActive = null,
) {}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class UpdateBeneficiaryRequestData extends Data
{
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,
#[DataCollectionOf(AddressData::class)]
public ?DataCollection $addresses = null,
#[DataCollectionOf(DocumentData::class)]
public ?DataCollection $documents = null,
public ?bool $isActive = null,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries;
use Spatie\LaravelData\Data;
class WalletBalanceData extends Data
{
public function __construct(
public string $walletType,
public string $freeBalance,
public string $heldBalance,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts;
use Gemini\AlfaBank\Data\PaginationData;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\DataCollection;
class BeneficiaryCollectionData extends Data
{
public function __construct(
public PaginationData $pagination,
public string $accountNumber,
#[DataCollectionOf(BeneficiaryData::class)]
public DataCollection $beneficiaries
) {}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts;
use Gemini\AlfaBank\Data\PaginationData;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\DataCollection;
class BeneficiaryData extends Data
{
public function __construct(
public string $beneficiaryId,
public string $type,
public ?array $personalInformation = null,
public ?string $inn = null,
public ?string $firstName = null,
public ?string $lastName = null,
public ?string $middleName = null,
public ?string $name = null,
public ?string $countryOfTaxResidence = null,
public ?string $phoneNumber = null,
public ?string $email = null,
public ?array $addresses = null,
public ?array $documents = null,
public ?bool $isActive = null,
public ?array $balances = null,
public ?bool $isBeneficiary = null,
public ?bool $isVpr = null,
) {}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts;
use Spatie\LaravelData\Data;
class CounterpartyRequestStatusData extends Data
{
public function __construct(
public string $requestId,
public string $status,
public ?string $beneficiaryId = null,
public ?string $universalRecipientId = null,
public ?string $description = null,
public ?string $resultData = null,
) {}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\PaginationData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class DealCollectionData extends Data
{
public function __construct(
public PaginationData $pagination,
#[DataCollectionOf(DealData::class)]
public DataCollection $deals
) {}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals;
use Spatie\LaravelData\Data;
class DealData extends Data
{
public function __construct(
public string $dealId,
public string $status,
public string $expireDate,
public bool $isValid,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals;
use Spatie\LaravelData\Data;
class DealPayoffData extends Data
{
public function __construct(
public string $stepId,
public string $payoffId,
public string $payoffType,
public string $deponentId,
public string $recipientId,
public string $amount,
public string $status,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals;
use Spatie\LaravelData\Data;
class DealRequestStatusData extends Data
{
public function __construct(
public string $requestId,
public string $status,
public ?string $description = null,
) {}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class ContractorActionRequestData extends Data
{
public function __construct(
public string $action,
public string $dealId,
public string $stepId,
public string $beneficiaryId,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
public ?string $amount = null,
public ?string $purpose = null,
#[DataCollectionOf(ContractorAmountData::class)]
public ?DataCollection $amounts = null,
public ?bool $keepOnNominalAccount = null,
public ?string $requisiteId = null,
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps;
use Spatie\LaravelData\Data;
class ContractorAmountData extends Data
{
public function __construct(
public ?string $walletType = null,
public ?string $amount = null,
) {}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class StepActionRequestData extends Data
{
public function __construct(
public string $dealId,
public string $action,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
public ?string $stepId = null,
public ?string $description = null,
) {}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps;
use Spatie\LaravelData\Data;
class StepActionResponseData extends Data
{
public function __construct(
public ?string $dealId = null,
public ?string $stepId = null,
public ?int $stepNumber = null,
public ?string $description = null,
public ?string $status = null,
public ?string $requestId = null,
) {}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class StepContractorData extends Data
{
public function __construct(
public ?string $beneficiaryId = null,
public ?string $role = null,
public ?string $amount = null,
public ?string $purpose = null,
public ?string $requisiteId = null,
public ?bool $keepOnNominalAccount = null,
#[DataCollectionOf(ContractorAmountData::class)]
public ?DataCollection $amounts = null,
) {}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps;
use Spatie\LaravelData\Data;
class StepData extends Data
{
public function __construct(
public string $stepId,
public int $stepNumber,
public string $description,
public string $status,
public bool $isValid,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Deals;
use Spatie\LaravelData\Data;
class ValidateDealResponseData extends Data
{
public function __construct(
public string $dealId,
public bool $isValid,
public ?array $description = null,
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Spatie\LaravelData\Data;
class AmountDistributionData extends Data
{
public function __construct(
public string $beneficiaryId,
public string $amount,
) {}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class IdentifyRequestData extends Data
{
public function __construct(
public string $transactionId,
public string $accountNumber,
#[DataCollectionOf(AmountDistributionData::class)]
public DataCollection $amountDistribution,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Spatie\LaravelData\Data;
class IdentifyResponseData extends Data
{
public function __construct(
public string $identifyId,
public string $status,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Spatie\LaravelData\Data;
class PayerData extends Data
{
public function __construct(
public ?string $inn = null,
public ?string $kpp = null,
public ?string $accountNumber = null,
public ?string $name = null,
public ?string $bankAccount = null,
public ?string $bankBik = null,
public ?string $bankName = null,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class RefundUnidentifiedTransactionRequestData extends Data
{
public function __construct(
public string $accountNumber,
public string $id,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Spatie\LaravelData\Data;
class RefundUnidentifiedTransactionResponseData extends Data
{
public function __construct(
public string $refundId,
public string $status,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class UnidentifiedTransactionCollectionData extends Data
{
public function __construct(
public int $offset,
public int $limit,
public int $totalCount,
#[DataCollectionOf(UnidentifiedTransactionData::class)]
public ?DataCollection $unidentifiedTransactions = null,
) {}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Identification;
use Spatie\LaravelData\Data;
class UnidentifiedTransactionData extends Data
{
public function __construct(
public string $id,
public string $amount,
public ?string $documentNumber = null,
public ?string $date = null,
public ?string $purpose = null,
public ?PayerData $payer = null,
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts;
use Spatie\LaravelData\Data;
class NominalAccountData extends Data
{
public function __construct(
public string $accountNumber,
public bool $isActive,
) {}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Spatie\LaravelData\Data;
class DetailedPayoffData extends Data
{
public function __construct(
public ?string $dealId = null,
public ?string $stepId = null,
public ?string $paymentType = null,
public ?string $requisiteType = null,
public ?array $payer = null,
public ?array $recipient = null,
public ?string $amount = null,
public ?string $purpose = null,
public ?string $status = null,
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Spatie\LaravelData\Data;
class DigestSignatureData extends Data
{
public function __construct(
public string $base64Encoded,
public string $signatureType,
) {}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class PayoffRequestData extends Data
{
public function __construct(
public string $type,
public string $accountNumber,
public RecipientData $recipient,
public PayoutRequisiteData $payoutRequisite,
public string $amount,
public string $purpose,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
public ?string $deponent = null,
public ?array $requisites = null,
public ?array $tax = null,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Spatie\LaravelData\Data;
class PayoffResponseData extends Data
{
public function __construct(
public string $payoffId,
public string $status,
public ?string $description = null,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Spatie\LaravelData\Data;
class PayoutRequisiteData extends Data
{
public function __construct(
public string $type,
public ?string $requisiteId = null,
public ?string $pan = null,
public ?string $phoneNumber = null,
public ?string $bankId = null,
public ?string $accountNumber = null,
public ?string $bik = null,
) {}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Spatie\LaravelData\Data;
class RecipientData extends Data
{
public function __construct(
public string $type,
public ?string $name = null,
public ?string $inn = null,
public ?string $kpp = null,
public ?string $beneficiaryId = null,
) {}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class TaxRefundRequestData extends Data
{
public function __construct(
public string $beneficiaryId,
public string $amount,
public string $purpose,
public bool $keepOnNominalAccount,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
public ?string $requisiteId = null,
) {}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\Payouts;
use Spatie\LaravelData\Data;
class TaxRefundResponseData extends Data
{
public function __construct(
public string $taxRefundId,
public string $status,
public ?string $description = null,
) {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\UniversalRecipients;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class CreateUniversalRecipientRequestData extends Data
{
public function __construct(
public string $accountNumber,
public string $type,
#[DataCollectionOf(DigestSignatureData::class)]
public DataCollection $digestSignatures,
) {}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\UniversalRecipients;
use Spatie\LaravelData\Data;
class UniversalRecipientData extends Data
{
public function __construct(
public string $universalRecipientId,
public bool $isBeneficiary,
public string $type,
public bool $isActive,
) {}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Gemini\AlfaBank\Data\NominalAccounts\UniversalRecipients;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryResponseData;
use Spatie\LaravelData\Data;
// Reuse response structure if identical (requestId, status)
class UniversalRecipientResponseData extends CreateBeneficiaryResponseData {}

View File

@@ -0,0 +1,14 @@
<?php
namespace Gemini\AlfaBank\Data;
use Spatie\LaravelData\Data;
class PaginationData extends Data
{
public function __construct(
public int $offset,
public int $limit,
public int $totalCount
) {}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Gemini\AlfaBank\Data\Sandbox\Payments;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
class CreateRubPaymentRequestData extends Data
{
public function __construct(
public string $externalId,
public float $amount,
public string $date,
public string $number,
public string $operationCode,
public string $payeeAccount,
public string $payeeBankBic,
public string $payeeInn,
public string $payeeName,
public string $payerAccount,
public string $payerBankBic,
public string $payerInn,
public string $payerName,
public string $purpose,
public string $priority = '5',
public string $deliveryKind = 'электронно',
public string $urgencyCode = 'NORMAL',
public ?string $payerKpp = null,
public ?string $payeeKpp = null,
public ?string $payerBankCorrAccount = null,
public ?string $payeeBankCorrAccount = null,
#[DataCollectionOf(DigestSignatureData::class)]
public ?DataCollection $digestSignatures = null,
public ?array $departmentalInfo = null,
public ?array $vat = null,
) {}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Gemini\AlfaBank\Data;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\DataCollection;
class WebhookCollectionData extends Data
{
public function __construct(
public PaginationData $pagination,
#[DataCollectionOf(WebhookData::class)]
public DataCollection $items
) {}
}

19
src/Data/WebhookData.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
namespace Gemini\AlfaBank\Data;
use Spatie\LaravelData\Data;
class WebhookData extends Data
{
public function __construct(
public string $id,
public string $object,
public bool $enabled,
public ?string $organizationIdHash,
public ?array $data,
public string $callbackUri,
public int $version,
public ?array $statusDescriptions
) {}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Gemini\AlfaBank\Enums;
enum Environment: string
{
case PRODUCTION = 'https://baas.alfabank.ru';
case SANDBOX = 'https://sandbox.alfabank.ru';
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Gemini\AlfaBank\Laravel;
use Gemini\AlfaBank\AlfaBankConnector;
use Gemini\AlfaBank\Enums\Environment;
use Gemini\AlfaBank\Sbp\SbpConnector;
use Illuminate\Support\ServiceProvider;
class AlfaBankServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/Config/alfa-bank.php', 'alfa-bank');
$this->app->singleton(AlfaBankConnector::class, function ($app) {
$config = $app['config']['alfa-bank.baas'];
return new AlfaBankConnector(
certificatePath: $config['certificate_path'],
privateKeyPath: $config['private_key_path'],
environment: $config['environment'] === 'production' ? Environment::PRODUCTION : Environment::SANDBOX
);
});
$this->app->singleton(SbpConnector::class, function ($app) {
$config = $app['config']['alfa-bank.sbp'];
return new SbpConnector(
certificatePath: $config['certificate_path'],
privateKeyPath: $config['private_key_path'],
keyNameAlias: $config['key_name_alias'],
environment: $config['environment'] === 'production' ? Environment::PRODUCTION : Environment::SANDBOX
);
});
}
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__ . '/Config/alfa-bank.php' => config_path('alfa-bank.php'),
], 'alfa-bank-config');
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
return [
'baas' => [
'certificate_path' => env('ALFA_BAAS_CERT_PATH'),
'private_key_path' => env('ALFA_BAAS_KEY_PATH'),
'environment' => env('ALFA_BAAS_ENV', 'sandbox'), // sandbox or production
],
'sbp' => [
'certificate_path' => env('ALFA_SBP_CERT_PATH'),
'private_key_path' => env('ALFA_SBP_KEY_PATH'),
'key_name_alias' => env('ALFA_SBP_KEY_ALIAS'),
'environment' => env('ALFA_SBP_ENV', 'sandbox'),
],
];

View File

@@ -0,0 +1,46 @@
<?php
namespace Gemini\AlfaBank\Requests\Auth;
use Gemini\AlfaBank\Data\Auth\TokenData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasFormBody;
use Saloon\Contracts\Body\HasBody;
class GetTokenRequest extends Request implements HasBody
{
use HasFormBody;
protected Method $method = Method::POST;
public function __construct(
protected string $clientId,
protected string $clientSecret,
protected string $code,
protected string $redirectUri,
protected string $grantType = 'authorization_code'
) {}
public function resolveEndpoint(): string
{
return '/oidc/token';
}
protected function defaultBody(): array
{
return [
'grant_type' => $this->grantType,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'code' => $this->code,
'redirect_uri' => $this->redirectUri,
];
}
public function createDtoFromResponse(Response $response): TokenData
{
return TokenData::from($response->json());
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gemini\AlfaBank\Requests\Auth;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class RefreshClientSecretRequest extends Request
{
protected Method $method = Method::POST;
public function __construct(
protected string $clientId
) {}
public function resolveEndpoint(): string
{
return "/oidc/clients/{$this->clientId}/client-secret";
}
public function createDtoFromResponse(Response $response): array
{
return $response->json();
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Gemini\AlfaBank\Requests\Auth;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasFormBody;
class RevokeTokenRequest extends Request implements HasBody
{
use HasFormBody;
protected Method $method = Method::POST;
public function __construct(
protected string $clientId,
protected string $clientSecret,
protected string $token,
protected ?string $tokenTypeHint = null
) {}
public function resolveEndpoint(): string
{
return '/oidc/revoke';
}
protected function defaultBody(): array
{
return array_filter([
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'token' => $this->token,
'token_type_hint' => $this->tokenTypeHint,
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Gemini\AlfaBank\Requests\Auth\UserInfo;
use Gemini\AlfaBank\Data\Auth\UserInfo\ClientConsentCollectionData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetClientConsentsRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected ?string $clientId = null,
protected ?string $sub = null,
protected ?string $organizationIdHash = null,
protected ?bool $isActive = null,
protected ?int $limit = 1000,
protected ?int $offset = 0,
) {}
public function resolveEndpoint(): string
{
return '/oidc/client-consents';
}
protected function defaultQuery(): array
{
return array_filter([
'clientId' => $this->clientId,
'sub' => $this->sub,
'organizationIdHash' => $this->organizationIdHash,
'isActive' => $this->isActive,
'limit' => $this->limit,
'offset' => $this->offset,
], fn($value) => $value !== null);
}
public function createDtoFromResponse(Response $response): ClientConsentCollectionData
{
return ClientConsentCollectionData::from($response->json());
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Gemini\AlfaBank\Requests\Auth\UserInfo;
use Gemini\AlfaBank\Data\Auth\UserInfo\UserInfoData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetUserInfoRequest extends Request
{
protected Method $method = Method::GET;
public function resolveEndpoint(): string
{
return '/oidc/userinfo';
}
protected function defaultHeaders(): array
{
return [
'Accept' => 'application/jwt', // As per doc
];
}
/**
* The response is a JWT. We need to decode it to get the claims.
* Note: This implementation assumes the user will handle JWT verification/decoding
* or we can do a simple base64 decode of the payload if trust is established via mTLS/OIDC.
*
* For now, we will return the raw JWT string in a specific DTO or try to parse it if simple.
*/
public function createDtoFromResponse(Response $response): UserInfoData
{
$jwt = $response->body();
$parts = explode('.', $jwt);
if (count($parts) === 3) {
$payload = json_decode(base64_decode($parts[1]), true);
return UserInfoData::from($payload);
}
// Fallback or throw error?
// If the response is not a valid JWT (e.g. error json), Saloon might have handled it if we throw on error status.
// But if it's 200 OK and not a JWT, it's unexpected.
throw new \Exception("Invalid UserInfo response format. Expected JWT.");
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gemini\AlfaBank\Requests\Crypto;
use Gemini\AlfaBank\Data\Crypto\CertificateData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\LaravelData\DataCollection;
class GetCertificatesRequest extends Request
{
protected Method $method = Method::GET;
public function resolveEndpoint(): string
{
return '/api/gd/v1/crypto-service/certificates';
}
public function createDtoFromResponse(Response $response): DataCollection
{
return CertificateData::collect($response->json(), DataCollection::class);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Gemini\AlfaBank\Requests\Crypto;
use Gemini\AlfaBank\Data\Crypto\CertificateData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
class UpdateCertificateRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::PATCH;
public function __construct(
protected string $id,
protected string $status, // ACTIVE or REVOKED
protected string $op = 'replace',
protected string $path = '/status'
) {}
public function resolveEndpoint(): string
{
return "/api/gd/v1/crypto-service/certificates/{$this->id}";
}
protected function defaultBody(): array
{
return [
'op' => $this->op,
'path' => $this->path,
'value' => $this->status,
];
}
public function createDtoFromResponse(Response $response): CertificateData
{
return CertificateData::from($response->json());
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Gemini\AlfaBank\Requests\Crypto;
use Gemini\AlfaBank\Data\Crypto\CertificateData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
class UploadCertificateRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::POST;
public function __construct(
protected string $type,
protected string $certificateBody,
) {}
public function resolveEndpoint(): string
{
return '/api/gd/v1/crypto-service/certificates';
}
protected function defaultBody(): array
{
return [
'type' => $this->type,
'certificateBody' => $this->certificateBody,
];
}
public function createDtoFromResponse(Response $response): CertificateData
{
return CertificateData::from($response->json());
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gemini\AlfaBank\Requests\FileExchange;
use Saloon\Enums\Method;
use Saloon\Http\Request;
class DownloadFileRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $downloadUrl
) {}
public function resolveEndpoint(): string
{
return $this->downloadUrl;
}
protected function defaultHeaders(): array
{
return [
'Accept' => '*/*',
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Gemini\AlfaBank\Requests\FileExchange;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasStreamBody;
class UploadFileRequest extends Request implements HasBody
{
use HasStreamBody;
protected Method $method = Method::PUT;
public function __construct(
protected string $uploadUrl,
protected $fileStream,
protected int $fileSize
) {}
public function resolveEndpoint(): string
{
return $this->uploadUrl;
}
protected function defaultHeaders(): array
{
return [
'Accept' => '*/*',
'Content-Length' => (string)$this->fileSize,
'Content-Type' => 'application/octet-stream',
];
}
protected function defaultBody(): mixed
{
return $this->fileStream;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryRequestData;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryResponseData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
class CreateBeneficiaryRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::POST;
public function __construct(
protected CreateBeneficiaryRequestData $data
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/beneficiaries';
}
protected function defaultBody(): array
{
return $this->data->toArray();
}
public function createDtoFromResponse(Response $response): CreateBeneficiaryResponseData
{
return CreateBeneficiaryResponseData::from($response->json());
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\BeneficiaryCollectionData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetBeneficiariesRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $accountNumber,
protected string $type,
protected ?int $limit = null,
protected ?int $offset = null,
protected ?string $inn = null,
protected ?string $lastName = null,
protected ?string $firstName = null,
protected ?string $middleName = null,
protected ?string $name = null,
protected ?bool $isActive = null
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/beneficiaries';
}
protected function defaultQuery(): array
{
return array_filter([
'accountNumber' => $this->accountNumber,
'type' => $this->type,
'limit' => $this->limit,
'offset' => $this->offset,
'inn' => $this->inn,
'lastName' => $this->lastName,
'firstName' => $this->firstName,
'middleName' => $this->middleName,
'name' => $this->name,
'isActive' => $this->isActive,
], fn($value) => $value !== null);
}
public function createDtoFromResponse(Response $response): BeneficiaryCollectionData
{
return BeneficiaryCollectionData::from($response->json());
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\BeneficiaryBalanceData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetBeneficiaryBalanceRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $beneficiaryId,
protected string $accountNumber,
protected string $date
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/{$this->beneficiaryId}/balance";
}
protected function defaultQuery(): array
{
return [
'accountNumber' => $this->accountNumber,
'date' => $this->date,
];
}
public function createDtoFromResponse(Response $response): BeneficiaryBalanceData
{
return BeneficiaryBalanceData::from($response->json());
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\BeneficiaryTransactionCollectionData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetBeneficiaryTransactionsRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $beneficiaryId,
protected string $dateFrom,
protected string $dateTo,
protected ?int $limit = 100,
protected ?int $offset = 0
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/{$this->beneficiaryId}/transactions";
}
protected function defaultQuery(): array
{
return [
'dateFrom' => $this->dateFrom,
'dateTo' => $this->dateTo,
'limit' => $this->limit,
'offset' => $this->offset,
];
}
public function createDtoFromResponse(Response $response): BeneficiaryTransactionCollectionData
{
return BeneficiaryTransactionCollectionData::from($response->json());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries\Requisites;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites\CreateRequisiteRequestData;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites\RequisiteData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
class AddRequisiteRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::POST;
public function __construct(
protected string $beneficiaryId,
protected CreateRequisiteRequestData $data
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/{$this->beneficiaryId}/requisites";
}
protected function defaultBody(): array
{
return $this->data->toArray();
}
public function createDtoFromResponse(Response $response): RequisiteData
{
return RequisiteData::from($response->json());
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries\Requisites;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites\RequisiteData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\LaravelData\DataCollection;
class GetRequisitesRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $beneficiaryId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/{$this->beneficiaryId}/requisites";
}
public function createDtoFromResponse(Response $response): DataCollection
{
return RequisiteData::collect($response->json(), DataCollection::class);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries\Requisites;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\Requisites\UpdateRequisiteRequestData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
class UpdateRequisiteRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::PATCH;
public function __construct(
protected string $beneficiaryId,
protected string $requisiteId,
protected UpdateRequisiteRequestData $data
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/{$this->beneficiaryId}/requisites/{$this->requisiteId}";
}
protected function defaultBody(): array
{
return $this->data->toArray();
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryResponseData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class ScoreBeneficiaryRequest extends Request
{
protected Method $method = Method::POST;
public function __construct(
protected string $beneficiaryId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/{$this->beneficiaryId}/scoring";
}
public function createDtoFromResponse(Response $response): CreateBeneficiaryResponseData
{
return CreateBeneficiaryResponseData::from($response->json());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Beneficiaries;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\CreateBeneficiaryResponseData;
use Gemini\AlfaBank\Data\NominalAccounts\Beneficiaries\UpdateBeneficiaryRequestData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
class UpdateBeneficiaryRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::PUT;
public function __construct(
protected string $beneficiaryId,
protected UpdateBeneficiaryRequestData $data
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/{$this->beneficiaryId}";
}
protected function defaultBody(): array
{
return $this->data->toArray();
}
public function createDtoFromResponse(Response $response): CreateBeneficiaryResponseData
{
return CreateBeneficiaryResponseData::from($response->json());
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\DealData;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
use Spatie\LaravelData\DataCollection;
class AddDealRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::POST;
public function __construct(
protected string $accountNumber,
protected string $expireDate,
#[DataCollectionOf(DigestSignatureData::class)]
protected DataCollection $digestSignatures
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/deals';
}
protected function defaultBody(): array
{
return [
'accountNumber' => $this->accountNumber,
'expireDate' => $this->expireDate,
'digestSignatures' => $this->digestSignatures->toArray(),
];
}
public function createDtoFromResponse(Response $response): DealData
{
return DealData::from($response->json());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\NominalAccounts\Payouts\DigestSignatureData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Spatie\LaravelData\DataCollection;
class DealActionRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::PATCH;
public function __construct(
protected string $dealId,
protected string $action, // ACCEPT, COMPLETE, CANCEL
#[DataCollectionOf(DigestSignatureData::class)]
protected DataCollection $digestSignatures
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/deals/action';
}
protected function defaultBody(): array
{
return [
'dealId' => $this->dealId,
'action' => $this->action,
'digestSignatures' => $this->digestSignatures->toArray(),
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\DealPayoffData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\LaravelData\DataCollection;
class GetDealPayoffsRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $dealId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/deals/{$this->dealId}/payoffs";
}
public function createDtoFromResponse(Response $response): DataCollection
{
return DealPayoffData::collect($response->json(), DataCollection::class);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\DealData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetDealRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $dealId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/deals/{$this->dealId}";
}
public function createDtoFromResponse(Response $response): DealData
{
return DealData::from($response->json());
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\DealRequestStatusData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetDealRequestStatusRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $dealId,
protected string $requestId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/deals/{$this->dealId}/requests/{$this->requestId}/status";
}
public function createDtoFromResponse(Response $response): DealRequestStatusData
{
return DealRequestStatusData::from($response->json());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\DealCollectionData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetDealsRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $accountNumber,
protected ?int $limit = 100,
protected ?int $offset = 0
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/deals';
}
protected function defaultQuery(): array
{
return [
'accountNumber' => $this->accountNumber,
'limit' => $this->limit,
'offset' => $this->offset,
];
}
public function createDtoFromResponse(Response $response): DealCollectionData
{
return DealCollectionData::from($response->json());
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals\Steps;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps\StepContractorData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\LaravelData\DataCollection;
class GetStepContractorsRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $dealId,
protected string $stepId,
protected ?string $contractorId = null,
protected ?string $role = null
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/deals/{$this->dealId}/steps/{$this->stepId}/contractors";
}
protected function defaultQuery(): array
{
return array_filter([
'contractorId' => $this->contractorId,
'role' => $this->role,
]);
}
public function createDtoFromResponse(Response $response): DataCollection
{
return StepContractorData::collect($response->json(), DataCollection::class);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals\Steps;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps\StepData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetStepRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $dealId,
protected string $stepId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/deals/{$this->dealId}/steps/{$this->stepId}";
}
public function createDtoFromResponse(Response $response): StepData
{
return StepData::from($response->json());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals\Steps;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps\StepData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\LaravelData\DataCollection;
class GetStepsRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $dealId,
protected ?int $limit = 50,
protected ?int $offset = 0
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/deals/{$this->dealId}/steps";
}
protected function defaultQuery(): array
{
return [
'limit' => $this->limit,
'offset' => $this->offset,
];
}
public function createDtoFromResponse(Response $response): DataCollection
{
return StepData::collect($response->json(), DataCollection::class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals\Steps;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps\ContractorActionRequestData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
class ManageContractorRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::PATCH;
public function __construct(
protected ContractorActionRequestData $data
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/deals/steps/contractors';
}
protected function defaultBody(): array
{
return $this->data->toArray();
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals\Steps;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps\StepActionRequestData;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\Steps\StepActionResponseData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Traits\Body\HasJsonBody;
class ManageStepRequest extends Request implements HasBody
{
use HasJsonBody;
protected Method $method = Method::POST;
public function __construct(
protected StepActionRequestData $data
) {}
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/deals/steps';
}
protected function defaultBody(): array
{
return $this->data->toArray();
}
public function createDtoFromResponse(Response $response): StepActionResponseData
{
return StepActionResponseData::from($response->json());
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Deals;
use Gemini\AlfaBank\Data\NominalAccounts\Deals\ValidateDealResponseData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class ValidateDealRequest extends Request
{
protected Method $method = Method::PATCH;
public function __construct(
protected string $dealId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/deals/{$this->dealId}/is-valid";
}
public function createDtoFromResponse(Response $response): ValidateDealResponseData
{
return ValidateDealResponseData::from($response->json());
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts;
use Gemini\AlfaBank\Data\NominalAccounts\CounterpartyRequestStatusData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetCounterpartyRequestStatusRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $requestId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/beneficiaries/requests/{$this->requestId}/status";
}
public function createDtoFromResponse(Response $response): CounterpartyRequestStatusData
{
return CounterpartyRequestStatusData::from($response->json());
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts;
use Gemini\AlfaBank\Data\NominalAccounts\NominalAccountData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\LaravelData\DataCollection;
class GetNominalAccountsRequest extends Request
{
protected Method $method = Method::GET;
public function resolveEndpoint(): string
{
return '/api/na/jp/v1/nominal-accounts';
}
public function createDtoFromResponse(Response $response): DataCollection
{
return NominalAccountData::collect($response->json(), DataCollection::class);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gemini\AlfaBank\Requests\NominalAccounts\Identification;
use Gemini\AlfaBank\Data\NominalAccounts\Identification\IdentifyResponseData;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
class GetIdentifyStatusRequest extends Request
{
protected Method $method = Method::GET;
public function __construct(
protected string $id,
protected string $identifyId
) {}
public function resolveEndpoint(): string
{
return "/api/na/jp/v1/unidentified-transactions/{$this->id}/identify/{$this->identifyId}";
}
public function createDtoFromResponse(Response $response): IdentifyResponseData
{
return IdentifyResponseData::from($response->json());
}
}

Some files were not shown because too many files have changed in this diff Show More