forked from apoloz/alfa-bank-sdk
refactor: dto, requests, connector
This commit is contained in:
@@ -119,5 +119,15 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable automated JWS response verification and decoding.
|
||||
*/
|
||||
public function withJwsVerification(string $publicKeyPem): static
|
||||
{
|
||||
$this->middleware()->onResponse(new \Apoloz\AlfaBank\Http\Middleware\VerifyJwsResponseMiddleware($publicKeyPem));
|
||||
|
||||
return $this;
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
19
src/Data/SelfEmployed/CreateIncomeRequestData.php
Normal file
19
src/Data/SelfEmployed/CreateIncomeRequestData.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Data\SelfEmployed;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class CreateIncomeRequestData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $vatin,
|
||||
public string $name,
|
||||
public string $amount,
|
||||
public string $date,
|
||||
public ?string $inn = null,
|
||||
public ?string $organizationName = null,
|
||||
) {}
|
||||
}
|
||||
15
src/Data/SelfEmployed/CreateIncomeResponseData.php
Normal file
15
src/Data/SelfEmployed/CreateIncomeResponseData.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Data\SelfEmployed;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class CreateIncomeResponseData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $requestId,
|
||||
public string $status,
|
||||
) {}
|
||||
}
|
||||
36
src/Http/Middleware/VerifyJwsResponseMiddleware.php
Normal file
36
src/Http/Middleware/VerifyJwsResponseMiddleware.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Http\Middleware;
|
||||
|
||||
use Apoloz\AlfaBank\Utils\JwsSigner;
|
||||
use Saloon\Http\Response;
|
||||
|
||||
class VerifyJwsResponseMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
protected ?string $publicKey = null
|
||||
) {}
|
||||
|
||||
public function __invoke(Response $response): Response
|
||||
{
|
||||
$contentType = $response->header('Content-Type');
|
||||
|
||||
if (str_contains((string)$contentType, 'application/jose')) {
|
||||
if (!$this->publicKey) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$jws = $response->body();
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,32 @@ class VerifyAlfaWebhook
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// 1. Check Basic Auth (Legacy/Simple)
|
||||
$user = config('alfa-bank.webhook.user');
|
||||
$password = config('alfa-bank.webhook.password');
|
||||
|
||||
if ($user && $password) {
|
||||
if ($request->getUser() !== $user || $request->getPassword() !== $password) {
|
||||
return response()->json(['error' => 'Unauthorized Webhook'], 401);
|
||||
return response()->json(['error' => 'Unauthorized Webhook (Basic Auth)'], 401);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check JWS Signature (Advanced)
|
||||
$publicKeyPath = config('alfa-bank.webhook.alfa_public_key');
|
||||
if ($publicKeyPath && file_exists($publicKeyPath)) {
|
||||
$signature = $request->header('x-jws-signature') ?: $request->getContent();
|
||||
|
||||
// If content is a JWS string (3 parts separated by dot)
|
||||
if (is_string($signature) && count(explode('.', $signature)) === 3) {
|
||||
$publicKey = file_get_contents($publicKeyPath);
|
||||
$verified = \Apoloz\AlfaBank\Utils\JwsSigner::verify($signature, $publicKey);
|
||||
|
||||
if (!$verified) {
|
||||
return response()->json(['error' => 'Invalid Webhook Signature'], 401);
|
||||
}
|
||||
|
||||
// Replace request content with verified payload
|
||||
$request->merge($verified);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
src/Requests/Accounts/GetAccountStatusRequest.php
Normal file
22
src/Requests/Accounts/GetAccountStatusRequest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Accounts;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class GetAccountStatusRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function __construct(
|
||||
protected string $accountNumber
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return "/api/gd/v1/accounts/{$this->accountNumber}/status";
|
||||
}
|
||||
}
|
||||
22
src/Requests/B2C/GetB2CPackageStatusRequest.php
Normal file
22
src/Requests/B2C/GetB2CPackageStatusRequest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\B2C;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class GetB2CPackageStatusRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function __construct(
|
||||
protected string $packageId
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return "/api/b2cpay/jp/v1/payments/packages/{$this->packageId}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\DigitalIdentity;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class GetAccountChannelStatusRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function __construct(
|
||||
protected string $accountNumber
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return "/api/gd/v1/accounts/{$this->accountNumber}/status";
|
||||
}
|
||||
}
|
||||
44
src/Requests/Files/UploadFileRequest.php
Normal file
44
src/Requests/Files/UploadFileRequest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Files;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @param string $url Full pre-signed URL from Alfa Bank
|
||||
* @param mixed $resource File resource or stream
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $url,
|
||||
protected $resource
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
protected function defaultHeaders(): array
|
||||
{
|
||||
return [
|
||||
'Accept' => '*/*',
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
];
|
||||
}
|
||||
|
||||
protected function defineBody(): mixed
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
}
|
||||
32
src/Requests/Partner/CreateIndividualRequest.php
Normal file
32
src/Requests/Partner/CreateIndividualRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Partner;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
use Saloon\Contracts\Body\HasBody;
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
use Saloon\Traits\Body\HasJsonBody;
|
||||
|
||||
class CreateIndividualRequest extends Request implements HasBody
|
||||
{
|
||||
use HasJsonBody;
|
||||
|
||||
protected Method $method = Method::POST;
|
||||
|
||||
public function __construct(
|
||||
protected array $data
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return '/partner/jp/v1/individuals';
|
||||
}
|
||||
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
22
src/Requests/Poa/DownloadPoaRequest.php
Normal file
22
src/Requests/Poa/DownloadPoaRequest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Poa;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class DownloadPoaRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function __construct(
|
||||
protected string $requestUuid
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return "/api/jp/v1/poa/requests/{$this->requestUuid}/download";
|
||||
}
|
||||
}
|
||||
22
src/Requests/Registries/GetRegistryPaymentsRequest.php
Normal file
22
src/Requests/Registries/GetRegistryPaymentsRequest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Registries;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class GetRegistryPaymentsRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function __construct(
|
||||
protected string $registryId
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return "/api/jp/v1/registries/{$this->registryId}/payments";
|
||||
}
|
||||
}
|
||||
22
src/Requests/Registries/GetRegistryRequest.php
Normal file
22
src/Requests/Registries/GetRegistryRequest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Registries;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class GetRegistryRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function __construct(
|
||||
protected string $registryId
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return "/api/jp/v1/registries/{$this->registryId}";
|
||||
}
|
||||
}
|
||||
18
src/Requests/SelfEmployed/GetRegionsRequest.php
Normal file
18
src/Requests/SelfEmployed/GetRegionsRequest.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\SelfEmployed;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class GetRegionsRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return '/semp/v1/dictionary/regions';
|
||||
}
|
||||
}
|
||||
39
src/Requests/SelfEmployed/RegisterIncomeRequest.php
Normal file
39
src/Requests/SelfEmployed/RegisterIncomeRequest.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\SelfEmployed;
|
||||
|
||||
use Apoloz\AlfaBank\Data\SelfEmployed\CreateIncomeRequestData;
|
||||
use Apoloz\AlfaBank\Data\SelfEmployed\CreateIncomeResponseData;
|
||||
use Saloon\Contracts\Body\HasBody;
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
use Saloon\Http\Response;
|
||||
use Saloon\Traits\Body\HasJsonBody;
|
||||
|
||||
class RegisterIncomeRequest extends Request implements HasBody
|
||||
{
|
||||
use HasJsonBody;
|
||||
|
||||
protected Method $method = Method::POST;
|
||||
|
||||
public function __construct(
|
||||
protected CreateIncomeRequestData $data
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return '/semp/v1/selfemployed/income';
|
||||
}
|
||||
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
return $this->data->toArray();
|
||||
}
|
||||
|
||||
public function createDtoFromResponse(Response $response): CreateIncomeResponseData
|
||||
{
|
||||
return CreateIncomeResponseData::from($response->json());
|
||||
}
|
||||
}
|
||||
18
src/Requests/Signature/GetDistinguishedNameRequest.php
Normal file
18
src/Requests/Signature/GetDistinguishedNameRequest.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Requests\Signature;
|
||||
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
|
||||
class GetDistinguishedNameRequest extends Request
|
||||
{
|
||||
protected Method $method = Method::GET;
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return '/jp/v3/signature/dn';
|
||||
}
|
||||
}
|
||||
33
src/Sbp/Data/Reversal/ReversalHistoryResponseData.php
Normal file
33
src/Sbp/Data/Reversal/ReversalHistoryResponseData.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Reversal;
|
||||
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
class ReversalHistoryResponseData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public int $ErrorCode,
|
||||
public string $message,
|
||||
#[DataCollectionOf(ReversalHistoryItemData::class)]
|
||||
public ?DataCollection $refunds = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
class ReversalHistoryItemData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $TermNo,
|
||||
public string $amount,
|
||||
public string $originalQrcId,
|
||||
public string $originalTrxId,
|
||||
public string $payerId,
|
||||
public string $status,
|
||||
public ?string $trxId = null,
|
||||
public ?string $trxDT = null,
|
||||
) {}
|
||||
}
|
||||
24
src/Sbp/Data/Webhooks/SbpPaymentNotificationData.php
Normal file
24
src/Sbp/Data/Webhooks/SbpPaymentNotificationData.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Webhooks;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class SbpPaymentNotificationData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $command,
|
||||
public string $qrcId,
|
||||
public string $trxId,
|
||||
public string $status,
|
||||
public string $amount,
|
||||
public string $timestamp,
|
||||
public string $payerId,
|
||||
public string $payrrn,
|
||||
public string $TermNo,
|
||||
public ?string $kzo = null,
|
||||
public ?string $CreditDatePos = null,
|
||||
) {}
|
||||
}
|
||||
27
src/Sbp/Data/Webhooks/SbpRefundNotificationData.php
Normal file
27
src/Sbp/Data/Webhooks/SbpRefundNotificationData.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Data\Webhooks;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class SbpRefundNotificationData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $command,
|
||||
public string $TermNo,
|
||||
public string $originalQrcId,
|
||||
public string $originalTrxId,
|
||||
public string $trxId,
|
||||
public string $trxDT,
|
||||
public string $status,
|
||||
public string $amount,
|
||||
public string $ErrorCode,
|
||||
public string $message,
|
||||
public string $payerId,
|
||||
public ?string $messageID = null,
|
||||
public ?string $agentRefundRequestId = null,
|
||||
public ?string $opkcRefundRequestId = null,
|
||||
) {}
|
||||
}
|
||||
43
src/Sbp/Requests/Reversal/GetQrcReversalHistoryRequest.php
Normal file
43
src/Sbp/Requests/Reversal/GetQrcReversalHistoryRequest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Sbp\Requests\Reversal;
|
||||
|
||||
use Apoloz\AlfaBank\Sbp\Data\Reversal\ReversalHistoryResponseData;
|
||||
use Saloon\Contracts\Body\HasBody;
|
||||
use Saloon\Enums\Method;
|
||||
use Saloon\Http\Request;
|
||||
use Saloon\Http\Response;
|
||||
use Saloon\Traits\Body\HasJsonBody;
|
||||
|
||||
class GetQrcReversalHistoryRequest extends Request implements HasBody
|
||||
{
|
||||
use HasJsonBody;
|
||||
|
||||
protected Method $method = Method::POST;
|
||||
|
||||
public function __construct(
|
||||
protected string $payrrn,
|
||||
protected string $TermNo,
|
||||
) {}
|
||||
|
||||
public function resolveEndpoint(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function defaultBody(): array
|
||||
{
|
||||
return [
|
||||
'command' => 'GetQRCreversalHistory',
|
||||
'TermNo' => $this->TermNo,
|
||||
'payrrn' => $this->payrrn,
|
||||
];
|
||||
}
|
||||
|
||||
public function createDtoFromResponse(Response $response): ReversalHistoryResponseData
|
||||
{
|
||||
return ReversalHistoryResponseData::from($response->json());
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,23 @@ class SbpConnector extends Connector implements HasPagination
|
||||
return $this->alfaConfig->baseUrl;
|
||||
}
|
||||
|
||||
protected function defaultConfig(): array
|
||||
/**
|
||||
* Parse payment notification from request body.
|
||||
*/
|
||||
public function parsePaymentNotification(array $data): \Apoloz\AlfaBank\Sbp\Data\Webhooks\SbpPaymentNotificationData
|
||||
{
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Webhooks\SbpPaymentNotificationData::from($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse refund notification from request body.
|
||||
*/
|
||||
public function parseRefundNotification(array $data): \Apoloz\AlfaBank\Sbp\Data\Webhooks\SbpRefundNotificationData
|
||||
{
|
||||
return \Apoloz\AlfaBank\Sbp\Data\Webhooks\SbpRefundNotificationData::from($data);
|
||||
}
|
||||
|
||||
protected function defaultHeaders(): array
|
||||
{
|
||||
$config = [
|
||||
'cert' => $this->alfaConfig->certificatePath,
|
||||
@@ -92,6 +108,7 @@ class SbpConnector extends Connector implements HasPagination
|
||||
|
||||
// Mutate the request to add SBP-specific headers and set the exact signed body.
|
||||
$pendingRequest->headers()->add('Authorization', $signatureBase64);
|
||||
$pendingRequest->headers()->add('key-name', $this->keyNameAlias);
|
||||
// The SBP API documentation specifies this content-type for signed requests.
|
||||
$pendingRequest->headers()->add('Content-Type', 'application/x-www-form-urlencoded');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user