// 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\Requests\NominalAccounts\Payouts\CreatePayoffRequest; use Apoloz\AlfaBank\Requests\NominalAccounts\Payouts\CreateTaxRefundRequest; use Apoloz\AlfaBank\Requests\NominalAccounts\Payouts\PayoffAcceptRequest; use Apoloz\AlfaBank\Requests\NominalAccounts\Payouts\TaxRefundAcceptRequest; use Apoloz\AlfaBank\Traits\HasAlfaErrorHandling; use Apoloz\AlfaBank\Traits\HasSafeRetries; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; 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; class AlfaBankConnector extends Connector implements HasPagination { use AlwaysThrowOnErrors; use AuthorizationCodeGrant; use HasAlfaErrorHandling; use HasTimeout; // Replaces the HasTries default handleRetry (always retry) with a safe policy: // transport failures are retried for read-only requests only, bank verdicts never. use HasSafeRetries; public function __construct( protected AlfaConfig $alfaConfig, protected ?SignerInterface $signer = null, protected bool $loggingEnabled = true, protected ?LoggerInterface $logger = null, ) { $this->tries = 3; $this->retryInterval = 500; $this->logger = $this->logger ?? new NullLogger; // Money operations: repeating them is a duplicate-payment risk, so they are // denied explicitly in addition to the read-only-method rule. $this->denyRetriesFor([ CreatePayoffRequest::class, PayoffAcceptRequest::class, CreateTaxRefundRequest::class, TaxRefundAcceptRequest::class, ]); } public function resolveBaseUrl(): string { return $this->alfaConfig->baseUrl; } public function boot(PendingRequest $pendingRequest): void { $pendingRequest->middleware()->onRequest(new RequestIdMiddleware); if ($this->loggingEnabled) { $pendingRequest->middleware()->onRequest(new LoggingRequestMiddleware($this->logger)); $pendingRequest->middleware()->onResponse(new LoggingResponseMiddleware($this->logger)); } } 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; } // // //
// public function getSigner(): SignerInterface { if (! $this->signer) { throw new \RuntimeException('No signer configured for this connector.'); } return $this->signer; } //
//
// public function withLogging(LoggerInterface $logger): static { $this->logger = $logger; $this->loggingEnabled = true; return $this; } public function withoutLogging(): static { $this->loggingEnabled = false; 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 ]; } //
}