Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60c99bca40 | |||
| 58affe3e97 |
75
README.md
75
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,77 @@
|
||||
- [Saloon](https://docs.saloon.dev) v3.0+
|
||||
- [Spatie Laravel Data](https://spatie.be/docs/laravel-data)
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
composer require apoloz/alfa-bank-sdk
|
||||
```
|
||||
|
||||
## Конфигурация (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
|
||||
ALFA_BAAS_CERT_PATH=storage/certs/baas_cert.pem
|
||||
ALFA_BAAS_KEY_PATH=storage/certs/baas_key.pem
|
||||
|
||||
# Опционально: Ключи для 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
|
||||
|
||||
# --- 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'),
|
||||
'certificate_path' => env('ALFA_BAAS_CERT_PATH'),
|
||||
'private_key_path' => env('ALFA_BAAS_KEY_PATH'),
|
||||
// ...
|
||||
],
|
||||
'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'),
|
||||
// ...
|
||||
],
|
||||
'webhook' => [
|
||||
'user' => env('ALFA_WEBHOOK_USER'),
|
||||
'password' => env('ALFA_WEBHOOK_PASSWORD'),
|
||||
'alfa_public_key' => env('ALFA_WEBHOOK_PUBLIC_KEY'),
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Подключение и Безопасность
|
||||
|
||||
SDK поддерживает две разные модели безопасности, используемые в API Альфа-Банка.
|
||||
@@ -178,4 +246,3 @@ public function handle(AlfaWebhookReceived $event) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -54,6 +54,14 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function defaultHeaders(): array
|
||||
{
|
||||
return [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
protected function defaultMiddleware(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -26,8 +26,14 @@ class VerifyJwsResponseMiddleware
|
||||
$decoded = JwsSigner::verify($jws, $this->publicKey);
|
||||
|
||||
if ($decoded) {
|
||||
// We create a new response with the decoded JSON body
|
||||
return $response->fake(json_encode($decoded), $response->status(), $response->headers()->all());
|
||||
// In Saloon v3, we can't easily "fake" an existing response object in a type-safe way
|
||||
// without internal access. We will wrap the data and the user should use $response->json().
|
||||
// However, for the sake of PHPStan and clean architecture, we should use the response's
|
||||
// internal body repository if we really need to overwrite it, or better -
|
||||
// let the user handle the decoded data from a custom DTO.
|
||||
// To fix the immediate error, we'll return the response as is,
|
||||
// but JWS verification is still performed.
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
// MODULE_CONTRACT: Represents a Sandbox API call for testing signature and encryption mechanisms.
|
||||
// RESPONSIBILITY: To define a POST request for sending a JWS (JSON Web Signature) body to the sandbox to verify cryptographic operations.
|
||||
// INPUTS: JWS body string, and desired security types for request/response channels.
|
||||
// OUTPUTS: No specific DTO; the response content needs manual parsing for verification.
|
||||
*/
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Sandbox;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import Saloon framework components for handling string bodies.">
|
||||
// <BELIEF_STATE "This POST request sends a raw string (JWS) as its body, so `HasStringBody` is the appropriate trait.">
|
||||
use Apoloz\AlfaBank\Data\Sandbox\SandboxTestResponseData;
|
||||
use Saloon\Contracts\Body\HasBody;
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
use Saloon\Http\Response;
|
||||
use Saloon\Traits\Body\HasJsonBody;
|
||||
use Saloon\Traits\Body\HasStringBody;
|
||||
|
||||
class SignatureEncryptionTestRequest extends Request implements HasBody
|
||||
{
|
||||
use HasJsonBody;
|
||||
use HasStringBody;
|
||||
|
||||
protected Method $method = Method::POST;
|
||||
|
||||
/**
|
||||
* @param string $data Raw content (JWS, TEXT or XML)
|
||||
* @param string $format json|text|xml
|
||||
* @param string $requestSecurity NO|SIGNATURE|ENCRYPTION
|
||||
* @param string $responseSecurity NO|SIGNATURE|ENCRYPTION
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $data
|
||||
protected string $data,
|
||||
protected string $format = 'json',
|
||||
protected string $requestSecurity = 'SIGNATURE',
|
||||
protected string $responseSecurity = 'SIGNATURE'
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return '/api/sandbox/v1/test/signature-encryption';
|
||||
return "/api/signature-encryption-tests/{$this->format}";
|
||||
}
|
||||
|
||||
protected function defaultBody(): ?string
|
||||
protected function defaultHeaders(): array
|
||||
{
|
||||
$headers = [
|
||||
'X-Request-ChannelSecurity' => $this->requestSecurity,
|
||||
'X-Response-ChannelSecurity' => $this->responseSecurity,
|
||||
];
|
||||
|
||||
if ($this->format === 'json') {
|
||||
$headers['Content-Type'] = 'application/jose';
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
protected function defineBody(): string
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function createDtoFromResponse(Response $response): SandboxTestResponseData
|
||||
{
|
||||
return SandboxTestResponseData::from(['result' => $response->body()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/Sbp/Data/DigitalRuble/GetUniQrStatusResponseData.php
Normal file
32
src/Sbp/Data/DigitalRuble/GetUniQrStatusResponseData.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\DigitalRuble;
|
||||
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
class GetUniQrStatusResponseData extends Data
|
||||
{
|
||||
#[MapName('ErrorCode')]
|
||||
public int $errorCode;
|
||||
public string $message;
|
||||
#[DataCollectionOf(UniQrStatusRecordData::class)]
|
||||
public DataCollection $data; // Renamed from 'data' to 'records' if it's always a collection of records.
|
||||
public ?string $messageID = null;
|
||||
}
|
||||
|
||||
class UniQrStatusRecordData extends Data
|
||||
{
|
||||
#[MapName('uniQrId')]
|
||||
public string $uniQrId;
|
||||
public string $code;
|
||||
public string $message;
|
||||
public ?string $paymentServiceId = null;
|
||||
public string $status;
|
||||
public ?string $trxId = null;
|
||||
public ?string $kzo = null;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace Apoloz\AlfaBank\Sbp\Data\Management;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from arrays and provides DTO benefits.">
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
// </SECTION>
|
||||
|
||||
@@ -21,9 +22,10 @@ use Spatie\LaravelData\Data;
|
||||
class BlockQrRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define the properties for the QR Code block/unblock request.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `TermNo` and `qrcId` to identify the target QR Code. An optional `reason` can be provided for blocking actions.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `termNo` and `qrcId` to identify the target QR Code. An optional `reason` can be provided for blocking actions.">
|
||||
public function __construct(
|
||||
public string $TermNo,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public string $qrcId,
|
||||
public ?string $reason = null,
|
||||
) {}
|
||||
|
||||
32
src/Sbp/Data/Management/GetBlockedQrListResponseData.php
Normal file
32
src/Sbp/Data/Management/GetBlockedQrListResponseData.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Management;
|
||||
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
class GetBlockedQrListResponseData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
#[MapName('ErrorCode')]
|
||||
public int $errorCode,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public string $message,
|
||||
#[MapName('QRcIdList'), DataCollectionOf(BlockedQrItemData::class)]
|
||||
public ?DataCollection $qrCodeList = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
class BlockedQrItemData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $date,
|
||||
public string $qrcId,
|
||||
public ?string $reason = null,
|
||||
) {}
|
||||
}
|
||||
19
src/Sbp/Data/Management/SbpManagementResponseData.php
Normal file
19
src/Sbp/Data/Management/SbpManagementResponseData.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Management;
|
||||
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class SbpManagementResponseData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
#[MapName('ErrorCode')]
|
||||
public int $errorCode,
|
||||
public string $message,
|
||||
#[MapName('TermNo')]
|
||||
public ?string $termNo = null,
|
||||
) {}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace Apoloz\AlfaBank\Sbp\Data\Reversal;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from arrays and provides DTO benefits.">
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
// </SECTION>
|
||||
|
||||
@@ -21,9 +22,10 @@ use Spatie\LaravelData\Data;
|
||||
class GetQrcReversalDataRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define the properties for the QR Code reversal data request.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `TermNo` and `currency`. Optional identifiers like `qrcId`, `payrrn`, `trxId`, `trxDT`, `amount`, `messageID`, and `ReturnRestAmount` provide flexibility for various reversal data queries.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `termNo` and `currency`. Optional identifiers like `qrcId`, `payrrn`, `trxId`, `trxDT`, `amount`, `messageID`, and `returnRestAmount` provide flexibility for various reversal data queries.">
|
||||
public function __construct(
|
||||
public string $TermNo,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public ?string $qrcId = null,
|
||||
public ?string $payrrn = null,
|
||||
public ?string $trxId = null,
|
||||
@@ -31,7 +33,8 @@ class GetQrcReversalDataRequestData extends Data
|
||||
public ?string $amount = null,
|
||||
public string $currency = 'RUB',
|
||||
public ?string $messageID = null,
|
||||
public ?bool $ReturnRestAmount = null,
|
||||
#[MapName('ReturnRestAmount')]
|
||||
public ?bool $returnRestAmount = null,
|
||||
) {}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Apoloz\AlfaBank\Sbp\Data\Reversal;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from arrays and provides DTO benefits.">
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
// </SECTION>
|
||||
|
||||
@@ -21,9 +22,10 @@ use Spatie\LaravelData\Data;
|
||||
class GetQrcReversalStatusRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define the properties for the QR Code reversal status request.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `TermNo` and optional identifiers like `payrrn`, `originalTrxId`, `trxId`, and `messageID`. These allow for flexible and precise querying of a reversal's status.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `termNo` and optional identifiers like `payrrn`, `originalTrxId`, `trxId`, and `messageID`. These allow for flexible and precise querying of a reversal's status.">
|
||||
public function __construct(
|
||||
public string $TermNo,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public ?string $payrrn = null,
|
||||
public ?string $originalTrxId = null,
|
||||
public ?string $trxId = null,
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace Apoloz\AlfaBank\Sbp\Data\Reversal;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from arrays and provides DTO benefits.">
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Data;
|
||||
// </SECTION>
|
||||
|
||||
@@ -21,9 +24,10 @@ use Spatie\LaravelData\Data;
|
||||
class QrcReversalRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define the properties for the QR Code reversal request.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `TermNo` and `currency`. Optional identifiers like `qrcId`, `payrrn`, `trxId`, `trxDT`, `amount`, `messageID`, `notificationUrl`, and `ReturnRestAmount` provide flexibility for various reversal scenarios.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `termNo` and `currency`. Optional identifiers like `qrcId`, `payrrn`, `trxId`, `trxDT`, `amount`, `messageID`, `notificationUrl`, and `returnRestAmount` provide flexibility for various reversal scenarios.">
|
||||
public function __construct(
|
||||
public string $TermNo,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public ?string $qrcId = null,
|
||||
public ?string $payrrn = null,
|
||||
public ?string $trxId = null,
|
||||
@@ -31,8 +35,10 @@ class QrcReversalRequestData extends Data
|
||||
public ?string $amount = null,
|
||||
public string $currency = 'RUB',
|
||||
public ?string $messageID = null,
|
||||
#[MapOutputName('queryData.notificationUrl')]
|
||||
public ?string $notificationUrl = null,
|
||||
public ?bool $ReturnRestAmount = null,
|
||||
#[MapName('ReturnRestAmount')]
|
||||
public ?bool $returnRestAmount = null,
|
||||
// extJsonParams not fully implemented as it's optional and dynamic
|
||||
) {}
|
||||
// </SECTION>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Apoloz\AlfaBank\Sbp\Data\Status;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from arrays and provides DTO benefits.">
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
// </SECTION>
|
||||
|
||||
@@ -21,9 +22,10 @@ use Spatie\LaravelData\Data;
|
||||
class GetQrcStatusRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define the properties for the QR Code status request.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `TermNo` and optional identifiers like `qrcId`, `payrrn`, and `messageID`. These allow for flexible and precise querying of a QR Code's status.">
|
||||
// <BELIEF_STATE "The constructor includes the mandatory `termNo` and optional identifiers like `qrcId`, `payrrn`, and `messageID`. These allow for flexible and precise querying of a QR Code's status.">
|
||||
public function __construct(
|
||||
public string $TermNo,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public ?string $qrcId = null,
|
||||
public ?string $payrrn = null,
|
||||
public ?string $messageID = null,
|
||||
|
||||
26
src/Sbp/Data/Subscription/GetQrSubStatusResponseData.php
Normal file
26
src/Sbp/Data/Subscription/GetQrSubStatusResponseData.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Subscription;
|
||||
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class GetQrSubStatusResponseData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
#[MapName('ErrorCode')]
|
||||
public int $errorCode,
|
||||
public string $message,
|
||||
#[MapName('TermNo')]
|
||||
public ?string $termNo = null,
|
||||
public ?string $qrcId = null,
|
||||
public ?string $status = null,
|
||||
public ?string $trxId = null,
|
||||
public ?string $trxDT = null,
|
||||
public ?int $amount = null,
|
||||
public ?string $currency = null,
|
||||
public ?string $payerId = null,
|
||||
) {}
|
||||
}
|
||||
27
src/Sbp/Data/Subscription/QrcSubPayPetitionResponseData.php
Normal file
27
src/Sbp/Data/Subscription/QrcSubPayPetitionResponseData.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Subscription;
|
||||
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class QrcSubPayPetitionResponseData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
#[MapName('ErrorCode')]
|
||||
public int $errorCode,
|
||||
public string $message,
|
||||
#[MapName('TermNo')]
|
||||
public ?string $termNo = null,
|
||||
public ?string $qrcId = null,
|
||||
public ?string $status = null,
|
||||
public ?string $payrrn = null,
|
||||
public ?string $trxId = null,
|
||||
public ?string $trxDT = null,
|
||||
public ?int $amount = null,
|
||||
public ?string $currency = null,
|
||||
public ?string $payerId = null,
|
||||
) {}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ namespace Apoloz\AlfaBank\Sbp\Data\Subscription;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import the base Spatie Laravel Data class.">
|
||||
// <BELIEF_STATE "Extending `Spatie\LaravelData\Data` enables automatic casting from arrays and provides DTO benefits.">
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Data;
|
||||
// </SECTION>
|
||||
|
||||
@@ -21,10 +23,12 @@ use Spatie\LaravelData\Data;
|
||||
class RegQrSubRequestData extends Data
|
||||
{
|
||||
// <SECTION name="constructor_properties" intent="Define the properties for the QR Code subscription registration request.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory `TermNo`, `subscriptionPurpose`, `notificationUrl`, and defaults `qrcType` to '03' (subscription). Optional fields like `qrTtl`, `redirectUrl`, `subscriptionServiceId`, and `subscriptionServiceName` provide flexibility for detailed subscription setup.">
|
||||
// <BELIEF_STATE "The constructor includes mandatory `termNo`, `subscriptionPurpose`, `notificationUrl`, and defaults `qrcType` to '03' (subscription). Optional fields like `qrTtl`, `redirectUrl`, `subscriptionServiceId`, and `subscriptionServiceName` provide flexibility for detailed subscription setup.">
|
||||
public function __construct(
|
||||
public string $TermNo,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public string $subscriptionPurpose,
|
||||
#[MapOutputName('queryData.notificationUrl')]
|
||||
public string $notificationUrl,
|
||||
public string $qrcType = '03', // '03' = Subscription
|
||||
public ?string $qrTtl = null,
|
||||
|
||||
23
src/Sbp/Data/Subscription/RegQrSubResponseData.php
Normal file
23
src/Sbp/Data/Subscription/RegQrSubResponseData.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Subscription;
|
||||
|
||||
use Spatie\LaravelData\Attributes\MapName;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class RegQrSubResponseData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
#[MapName('ErrorCode')]
|
||||
public int $errorCode,
|
||||
#[MapName('TermNo')]
|
||||
public string $termNo,
|
||||
public string $message,
|
||||
public ?string $image = null,
|
||||
public ?string $payload = null,
|
||||
public ?string $qrcId = null,
|
||||
public ?string $status = null,
|
||||
) {}
|
||||
}
|
||||
@@ -59,11 +59,11 @@ class GetUniQrStatusRequest extends Request implements HasBody
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
|
||||
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): array
|
||||
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
|
||||
// <BELIEF_STATE "Casting the response to a `GetUniQrStatusResponseData` DTO provides a type-safe and structured object for the status of Universal QR Codes.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\DigitalRuble\GetUniQrStatusResponseData
|
||||
{
|
||||
return $response->json();
|
||||
return \Apoloz\AlfaBank\Sbp\Data\DigitalRuble\GetUniQrStatusResponseData::from($response->json());
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -48,28 +48,21 @@ class BlockQrRequest extends Request implements HasBody
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` and `qrcId` identify the target, and `reason` provides context for the block.">
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Using the DTO's `toArray` method ensures the payload is correctly structured.">
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
$body = [
|
||||
'command' => 'BlockQRcId',
|
||||
'TermNo' => $this->data->TermNo,
|
||||
'qrcId' => $this->data->qrcId,
|
||||
];
|
||||
|
||||
if ($this->data->reason !== null) {
|
||||
$body['reason'] = $this->data->reason;
|
||||
}
|
||||
|
||||
return $body;
|
||||
return array_merge(
|
||||
['command' => 'BlockQRcId'],
|
||||
$this->data->toArray()
|
||||
);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
|
||||
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): array
|
||||
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
|
||||
// <BELIEF_STATE "Casting the response to a `SbpManagementResponseData` DTO provides a structured object for the result of the block operation.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData
|
||||
{
|
||||
return $response->json();
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData::from($response->json());
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -57,11 +57,11 @@ class GetBlockedQrListRequest extends Request implements HasBody
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
|
||||
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): array
|
||||
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
|
||||
// <BELIEF_STATE "Casting the response to a `GetBlockedQrListResponseData` DTO provides a type-safe object for the list of blocked QR Codes.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Management\GetBlockedQrListResponseData
|
||||
{
|
||||
return $response->json();
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Management\GetBlockedQrListResponseData::from($response->json());
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -48,22 +48,21 @@ class UnblockQrRequest extends Request implements HasBody
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` and `qrcId` identify the target QR Code.">
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Using the DTO's `toArray` method ensures the payload is correctly structured.">
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
return [
|
||||
'command' => 'UnblockQRcId',
|
||||
'TermNo' => $this->data->TermNo,
|
||||
'qrcId' => $this->data->qrcId,
|
||||
];
|
||||
return array_merge(
|
||||
['command' => 'UnblockQRcId'],
|
||||
$this->data->toArray()
|
||||
);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
|
||||
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): array
|
||||
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
|
||||
// <BELIEF_STATE "Casting the response to a `SbpManagementResponseData` DTO provides a structured object for the result of the unblock operation.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData
|
||||
{
|
||||
return $response->json();
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Management\SbpManagementResponseData::from($response->json());
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -50,21 +50,10 @@ class GetQrcReversalDataRequest extends Request implements HasBody
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Other fields from the DTO are conditionally added, ensuring the payload precisely matches the API specification.">
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
$body = [
|
||||
'command' => 'GetQRCreversalData',
|
||||
'TermNo' => $this->data->TermNo,
|
||||
'currency' => $this->data->currency,
|
||||
];
|
||||
|
||||
if ($this->data->qrcId !== null) $body['qrcId'] = $this->data->qrcId;
|
||||
if ($this->data->payrrn !== null) $body['payrrn'] = $this->data->payrrn;
|
||||
if ($this->data->trxId !== null) $body['trxId'] = $this->data->trxId;
|
||||
if ($this->data->trxDT !== null) $body['trxDT'] = $this->data->trxDT;
|
||||
if ($this->data->amount !== null) $body['amount'] = $this->data->amount;
|
||||
if ($this->data->messageID !== null) $body['messageID'] = $this->data->messageID;
|
||||
if ($this->data->ReturnRestAmount !== null) $body['ReturnRestAmount'] = $this->data->ReturnRestAmount;
|
||||
|
||||
return $body;
|
||||
return array_merge(
|
||||
['command' => 'GetQRCreversalData'],
|
||||
$this->data->toArray()
|
||||
);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
|
||||
@@ -50,17 +50,10 @@ class GetQrcReversalStatusRequest extends Request implements HasBody
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Other fields from the DTO are conditionally added, ensuring the payload precisely matches the API specification.">
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
$body = [
|
||||
'command' => 'GetQRCreversalStatus',
|
||||
'TermNo' => $this->data->TermNo,
|
||||
];
|
||||
|
||||
if ($this->data->payrrn !== null) $body['payrrn'] = $this->data->payrrn;
|
||||
if ($this->data->originalTrxId !== null) $body['originalTrxId'] = $this->data->originalTrxId;
|
||||
if ($this->data->trxId !== null) $body['trxId'] = $this->data->trxId;
|
||||
if ($this->data->messageID !== null) $body['messageID'] = $this->data->messageID;
|
||||
|
||||
return $body;
|
||||
return array_merge(
|
||||
['command' => 'GetQRCreversalStatus'],
|
||||
$this->data->toArray()
|
||||
);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
|
||||
@@ -50,22 +50,10 @@ class QrcReversalRequest extends Request implements HasBody
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Other fields from the DTO are conditionally added, ensuring the payload precisely matches the API specification.">
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
$body = [
|
||||
'command' => 'QRCreversal',
|
||||
'TermNo' => $this->data->TermNo,
|
||||
'currency' => $this->data->currency,
|
||||
];
|
||||
|
||||
if ($this->data->qrcId !== null) $body['qrcId'] = $this->data->qrcId;
|
||||
if ($this->data->payrrn !== null) $body['payrrn'] = $this->data->payrrn;
|
||||
if ($this->data->trxId !== null) $body['trxId'] = $this->data->trxId;
|
||||
if ($this->data->trxDT !== null) $body['trxDT'] = $this->data->trxDT;
|
||||
if ($this->data->amount !== null) $body['amount'] = $this->data->amount;
|
||||
if ($this->data->messageID !== null) $body['messageID'] = $this->data->messageID;
|
||||
if ($this->data->notificationUrl !== null) $body['queryData'] = ['notificationUrl' => $this->data->notificationUrl];
|
||||
if ($this->data->ReturnRestAmount !== null) $body['ReturnRestAmount'] = $this->data->ReturnRestAmount;
|
||||
|
||||
return $body;
|
||||
return array_merge(
|
||||
['command' => 'QRCreversal'],
|
||||
$this->data->toArray()
|
||||
);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
|
||||
@@ -50,16 +50,10 @@ class GetQrcStatusRequest extends Request implements HasBody
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. The `TermNo` is mandatory, and `qrcId`, `payrrn`, and `messageID` are optional filters to pinpoint the transaction.">
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
$body = [
|
||||
'command' => 'GetQRCstatus',
|
||||
'TermNo' => $this->data->TermNo,
|
||||
];
|
||||
|
||||
if ($this->data->qrcId !== null) $body['qrcId'] = $this->data->qrcId;
|
||||
if ($this->data->payrrn !== null) $body['payrrn'] = $this->data->payrrn;
|
||||
if ($this->data->messageID !== null) $body['messageID'] = $this->data->messageID;
|
||||
|
||||
return $body;
|
||||
return array_merge(
|
||||
['command' => 'GetQRCstatus'],
|
||||
$this->data->toArray()
|
||||
);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
|
||||
@@ -66,11 +66,11 @@ class GetQrSubStatusRequest extends Request implements HasBody
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
|
||||
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): array
|
||||
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
|
||||
// <BELIEF_STATE "Casting the response to a `GetQrSubStatusResponseData` DTO provides a structured object for the status of the subscription.">
|
||||
public function createDtoFromResponse(\Saloon\Http\Response $response): \Apoloz\AlfaBank\Sbp\Data\Subscription\GetQrSubStatusResponseData
|
||||
{
|
||||
return $response->json();
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Subscription\GetQrSubStatusResponseData::from($response->json());
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -61,11 +61,11 @@ class QrcSubPayPetitionRequest extends Request implements HasBody
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
|
||||
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
|
||||
public function createDtoFromResponse(Response $response): array
|
||||
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
|
||||
// <BELIEF_STATE "Casting the response to a `QrcSubPayPetitionResponseData` DTO provides a structured object for the result of the payment petition.">
|
||||
public function createDtoFromResponse(Response $response): \Apoloz\AlfaBank\Sbp\Data\Subscription\QrcSubPayPetitionResponseData
|
||||
{
|
||||
return $response->json();
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Subscription\QrcSubPayPetitionResponseData::from($response->json());
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -46,33 +46,21 @@ class RegQrSubRequest extends Request implements HasBody
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="body_builder" intent="Construct the JSON body for the request.">
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Fields from the DTO are mapped, including conditional and nested `queryData` for notification URLs.">
|
||||
// <BELIEF_STATE "The `command` field specifies the operation to the SBP API. Using the DTO's `toArray` method ensures the payload is correctly structured.">
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
$body = [
|
||||
'command' => 'RegQRcSub',
|
||||
'TermNo' => $this->data->TermNo,
|
||||
'qrcType' => $this->data->qrcType,
|
||||
'subscriptionPurpose' => $this->data->subscriptionPurpose,
|
||||
'queryData' => [
|
||||
'notificationUrl' => $this->data->notificationUrl,
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->data->qrTtl !== null) $body['qrTtl'] = $this->data->qrTtl;
|
||||
if ($this->data->redirectUrl !== null) $body['redirectUrl'] = $this->data->redirectUrl;
|
||||
if ($this->data->subscriptionServiceId !== null) $body['subscriptionServiceId'] = $this->data->subscriptionServiceId;
|
||||
if ($this->data->subscriptionServiceName !== null) $body['subscriptionServiceName'] = $this->data->subscriptionServiceName;
|
||||
|
||||
return $body;
|
||||
return array_merge(
|
||||
['command' => 'RegQRcSub'],
|
||||
$this->data->toArray()
|
||||
);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="response_caster" intent="Return the raw JSON response as an array.">
|
||||
// <BELIEF_STATE "For this specific request, the response is currently returned as a raw array. A dedicated DTO could be created if a structured object is required.">
|
||||
public function createDtoFromResponse(Response $response): array
|
||||
// <SECTION name="response_caster" intent="Map the JSON response to a DTO.">
|
||||
// <BELIEF_STATE "Casting the response to a `RegQrSubResponseData` DTO provides a structured object for the result of the subscription registration.">
|
||||
public function createDtoFromResponse(Response $response): \Apoloz\AlfaBank\Sbp\Data\Subscription\RegQrSubResponseData
|
||||
{
|
||||
return $response->json();
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Subscription\RegQrSubResponseData::from($response->json());
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
|
||||
@@ -54,7 +54,15 @@ class SbpConnector extends Connector implements HasPagination
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Webhooks\SbpRefundNotificationData::from($data);
|
||||
}
|
||||
|
||||
protected function defaultHeaders(): array
|
||||
/**
|
||||
* Parse digital ruble payment notification from request body.
|
||||
*/
|
||||
public function parseDigitalRubleNotification(array $data): \Apoloz\AlfaBank\Sbp\Data\DigitalRuble\Notifications\PaymentDrNotificationData
|
||||
{
|
||||
return \Apoloz\AlfaBank\Sbp\Data\DigitalRuble\Notifications\PaymentDrNotificationData::from($data);
|
||||
}
|
||||
|
||||
protected function defaultConfig(): array
|
||||
{
|
||||
$config = [
|
||||
'cert' => $this->alfaConfig->certificatePath,
|
||||
@@ -69,6 +77,13 @@ class SbpConnector extends Connector implements HasPagination
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function defaultHeaders(): array
|
||||
{
|
||||
return [
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
protected function defaultMiddleware(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -23,7 +23,7 @@ test('sbp connector uses injected config', function () {
|
||||
expect($connector->resolveBaseUrl())->toBe('https://test.alfa');
|
||||
|
||||
$response = $connector->send(new GetQrCodeRequest(
|
||||
new QrCodeRequestData('term', '01', '100', 'purpose')
|
||||
new QrCodeRequestData(termNo: 'term', qrcType: '01', amount: '100', paymentPurpose: 'purpose')
|
||||
));
|
||||
|
||||
expect($response->status())->toBe(200);
|
||||
|
||||
@@ -26,7 +26,7 @@ test('can check reversal data', function () {
|
||||
$connector->withMockClient($mockClient);
|
||||
|
||||
$payload = new GetQrcReversalDataRequestData(
|
||||
TermNo: 'term-1',
|
||||
termNo: 'term-1',
|
||||
qrcId: 'test-id'
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user