Files
alfa-bank-sdk/src/Core/AlfaBankConnector.php
2026-06-03 10:07:00 +03:00

171 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Apoloz\AlfaBank\Core;
// <SECTION name="module_imports" intent="Import necessary contracts, services, and base framework components for the connector's operation.">
// <BELIEF_STATE "Saloon's plugin architecture requires importing traits for features like retries and timeouts. Local contracts (SignerInterface) are imported for dependency injection.">
use Apoloz\AlfaBank\Contracts\SignerInterface;
use Apoloz\AlfaBank\Enums\Environment;
use Apoloz\AlfaBank\Http\Middleware\LoggingRequestMiddleware;
use Apoloz\AlfaBank\Http\Middleware\LoggingResponseMiddleware;
use Apoloz\AlfaBank\Http\Middleware\RequestIdMiddleware;
use Apoloz\AlfaBank\Http\Middleware\VerifyJwsResponseMiddleware;
use Apoloz\AlfaBank\Http\Pagination\AlfaOffsetPaginator;
use Apoloz\AlfaBank\Traits\HasAlfaErrorHandling;
use Psr\Log\LoggerInterface;
use Saloon\Helpers\OAuth2\OAuthConfig;
use Saloon\Http\Connector;
use Saloon\Http\OAuth2\GetClientCredentialsTokenRequest;
use Saloon\Http\PendingRequest;
use Saloon\Http\Request;
use Saloon\PaginationPlugin\Contracts\HasPagination;
use Saloon\PaginationPlugin\OffsetPaginator;
use Saloon\Traits\OAuth2\AuthorizationCodeGrant;
use Saloon\Traits\Plugins\AlwaysThrowOnErrors;
use Saloon\Traits\Plugins\HasTimeout;
use Saloon\Traits\RequestProperties\HasTries;
class AlfaBankConnector extends Connector implements HasPagination
{
use AlwaysThrowOnErrors;
use AuthorizationCodeGrant;
use HasAlfaErrorHandling;
use HasTimeout;
use HasTries;
public function __construct(
protected AlfaConfig $alfaConfig,
protected ?SignerInterface $signer = null
) {
$this->tries = 3;
$this->retryInterval = 500;
}
public function resolveBaseUrl(): string
{
return $this->alfaConfig->baseUrl;
}
public function boot(PendingRequest $pendingRequest): void
{
//
}
public function paginate(Request $request): OffsetPaginator
{
return new AlfaOffsetPaginator($this, $request);
}
// login() kept for ClientCredentials compatibility (legacy/simple flow)
public function login(?string $clientId = null, ?string $clientSecret = null, array $scopes = []): static
{
$oauthConfig = $this->oauthConfig();
// Allow overriding credentials via method arguments
if ($clientId) {
$oauthConfig->setClientId($clientId);
}
if ($clientSecret) {
$oauthConfig->setClientSecret($clientSecret);
}
if (empty($oauthConfig->getClientId()) || empty($oauthConfig->getClientSecret())) {
throw new \RuntimeException('Client ID/Secret not configured.');
}
$request = new GetClientCredentialsTokenRequest($oauthConfig, $scopes);
$response = $this->send($request);
// Use the trait's helper to parse the token
$authenticator = $this->createOAuthAuthenticatorFromResponse($response);
$this->authenticate($authenticator);
return $this;
}
// </SECTION>
// </SECTION>
// <SECTION name="signer_accessor" intent="Provide access to the configured signing service for requests that require a digital signature.">
// <BELIEF_STATE "Requests should not instantiate their own signing logic. The connector acts as a service locator, providing the globally-configured Signer service. This ensures that all signatures are generated consistently.">
public function getSigner(): SignerInterface
{
if (! $this->signer) {
throw new \RuntimeException('No signer configured for this connector.');
}
return $this->signer;
}
// </SECTION>
// <SECTION name="logging_mutator" intent="Allow attaching a PSR-3 logger to the connector for debugging.">
// <BELIEF_STATE "Logging should be an optional, opt-in feature. This method provides a fluent interface to attach a logging middleware, which will automatically sanitize sensitive data before output.">
public function withLogging(LoggerInterface $logger): static
{
$this->middleware()->onRequest(new LoggingRequestMiddleware($logger));
$this->middleware()->onResponse(new LoggingResponseMiddleware($logger));
return $this;
}
/**
* Enable automated JWS response verification and decoding.
*/
public function withJwsVerification(string $publicKeyPem): static
{
$this->middleware()->onResponse(new VerifyJwsResponseMiddleware($publicKeyPem));
return $this;
}
protected function defaultOauthConfig(): OAuthConfig
{
$idUrl = $this->alfaConfig->baseUrl === Environment::PRODUCTION->value
? Environment::ID_PRODUCTION->value
: Environment::ID_SANDBOX->value;
return OAuthConfig::make()
->setClientId($this->alfaConfig->clientId ?? '')
->setClientSecret($this->alfaConfig->clientSecret ?? '')
->setRedirectUri($this->alfaConfig->redirectUri ?? '')
->setAuthorizeEndpoint($idUrl.'/oidc/authorize')
->setTokenEndpoint('/oidc/token')
->setAllowBaseUrlOverride();
}
protected function defaultConfig(): array
{
$config = [
'cert' => $this->alfaConfig->certificatePath,
'ssl_key' => $this->alfaConfig->privateKeyPath,
'verify' => true,
];
if ($this->alfaConfig->caBundlePath) {
$config['verify'] = $this->alfaConfig->caBundlePath === 'false' ? false : $this->alfaConfig->caBundlePath;
}
return $config;
}
protected function defaultHeaders(): array
{
return [
'Accept' => 'application/json',
// Content-Type не устанавливаем здесь, каждый Request сам определяет его через трейты (HasJsonBody, HasFormBody и т.д.)
// OAuth токен запросы требуют application/x-www-form-urlencoded
];
}
protected function defaultMiddleware(): array
{
return [
new RequestIdMiddleware,
];
}
// </SECTION>
}