Skip to content

Commit

Permalink
Merge pull request #96 from world4youcom/add_retry_mechanism_via_requ…
Browse files Browse the repository at this point in the history
…est_header

Add retry mechanism via request header
  • Loading branch information
sprain authored Jul 24, 2024
2 parents d60e2a2 + 2c0d1f4 commit 625af7c
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 6 deletions.
67 changes: 67 additions & 0 deletions example/Transaction/example-authorize-retry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php declare(strict_types=1);

use Ticketpark\SaferpayJson\Request\Exception\SaferpayErrorException;
use Ticketpark\SaferpayJson\Request\RequestConfig;
use Ticketpark\SaferpayJson\Request\Transaction\AuthorizeRequest;

require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../credentials.php';

// A token you received after initializing a transaction (see example-initialize.php)

$token = 'xxx';

// The request ID you received after the first authorize request fails with a Behavior of RETRY or RETRY_LATER

$requestId = 'your_request_id';

// retryIndicator is set to 1 to indicate that this is a retry (see SaferpayJson/Request/RequestConfig.php)

$retryIndicator = 1;

// -----------------------------
// Step 1:
// Prepare the authorize request
// See https://saferpay.github.io/jsonapi/#Payment_v1_Transaction_Authorize
//
// Note: The RequestConfig is created with a requestId and retryIndicator to indicate that this is a retry
// (see https://docs.saferpay.com/home/integration-guide/licences-and-interfaces/error-handling#the-requestid-and-retryindicator)

$requestConfig = new RequestConfig(
$apiKey,
$apiSecret,
$customerId,
true,
$requestId,
$retryIndicator
);

// -----------------------------
// Step 2:
// Create the request with required data

$authorizeRequest = new AuthorizeRequest(
$requestConfig,
$token,
);

// Note: More data can be provided to InitializeRequest with setters,
// for example: $authorizeRequest->setCondition()
// See Saferpay documentation for available options.

// -----------------------------
// Step 3:
// Execute and check for successful response

try {
$response = $authorizeRequest->execute();
} catch (SaferpayErrorException $e) {
die ($e->getErrorResponse()->getErrorMessage());
}

echo 'The transaction has been successful! Transaction id: ' . $response->getTransaction()->getId() . "\n";

// -----------------------------
// Step 4:
// Capture the transaction to get the cash flowing.
// see: example-capture.php
8 changes: 6 additions & 2 deletions lib/SaferpayJson/Request/Container/RequestHeader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Ticketpark\SaferpayJson\Request\Container;

use JMS\Serializer\Annotation\SerializedName;
use Ticketpark\SaferpayJson\Request\RequestConfig;

final class RequestHeader
{
Expand Down Expand Up @@ -33,8 +34,11 @@ final class RequestHeader
*/
private ?ClientInfo $clientInfo = null;

public function __construct(string $customerId, string $requestId = null, int $retryIndicator = 0)
{
public function __construct(
string $customerId,
string $requestId = null,
int $retryIndicator = RequestConfig::MIN_RETRY_INDICATOR
) {
$this->customerId = $customerId;
$this->requestId = $requestId;
$this->retryIndicator = $retryIndicator;
Expand Down
4 changes: 3 additions & 1 deletion lib/SaferpayJson/Request/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public function __construct(RequestConfig $requestConfig)
public function getRequestHeader(): RequestHeader
{
return new RequestHeader(
$this->requestConfig->getCustomerId()
$this->requestConfig->getCustomerId(),
$this->requestConfig->getRequestId(),
$this->requestConfig->getRetryIndicator()
);
}

Expand Down
38 changes: 36 additions & 2 deletions lib/SaferpayJson/Request/RequestConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,45 @@
namespace Ticketpark\SaferpayJson\Request;

use GuzzleHttp\Client;
use InvalidArgumentException;

final class RequestConfig
{
public const MIN_RETRY_INDICATOR = 0;
public const MAX_RETRY_INDICATOR = 9;

private string $apiKey;
private string $apiSecret;
private string $customerId;
private bool $test;
private ?Client $client = null;
private ?string $requestId;
private int $retryIndicator;

public function __construct(string $apiKey, string $apiSecret, string $customerId, bool $test = false)
{
public function __construct(
string $apiKey,
string $apiSecret,
string $customerId,
bool $test = false,
?string $requestId = null,
int $retryIndicator = 0
) {
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->customerId = $customerId;
$this->test = $test;

if ($retryIndicator < self::MIN_RETRY_INDICATOR || $retryIndicator > self::MAX_RETRY_INDICATOR) {
throw new InvalidArgumentException('Retry indicator range: inclusive between '
. self::MIN_RETRY_INDICATOR . ' and ' . self::MAX_RETRY_INDICATOR);
}

if ($retryIndicator > self::MIN_RETRY_INDICATOR && $requestId === null) {
throw new InvalidArgumentException('Request id must be set if retry indicator is greater than 0');
}

$this->requestId = $requestId;
$this->retryIndicator = $retryIndicator;
}

public function getApiKey(): string
Expand Down Expand Up @@ -57,4 +81,14 @@ public function getClient(): Client

return $this->client;
}

public function getRequestId(): ?string
{
return $this->requestId;
}

public function getRetryIndicator(): int
{
return $this->retryIndicator;
}
}
36 changes: 35 additions & 1 deletion tests/SaferpayJson/Tests/Request/CommonRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response as GuzzleResponse;
use GuzzleHttp\Psr7\Utils as GuzzleUtils;
use InvalidArgumentException;
use JMS\Serializer\SerializerBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
Expand All @@ -30,6 +31,39 @@ public function testErrorResponse(): void
$this->executeRequest();
}

public function getRequestConfigValidationParams(): array
{
return [
'first try' => [null, RequestConfig::MIN_RETRY_INDICATOR],
'second try' => [uniqid(), RequestConfig::MIN_RETRY_INDICATOR + 1],
'last try' => [uniqid(), RequestConfig::MAX_RETRY_INDICATOR],
'try after all retries exceeded' => [uniqid(), RequestConfig::MAX_RETRY_INDICATOR + 1, InvalidArgumentException::class],
'retry without previous request id' => [null, RequestConfig::MAX_RETRY_INDICATOR, InvalidArgumentException::class],
];
}

/**
* @dataProvider getRequestConfigValidationParams
*/
public function testRequestConfigValidation(
?string $requestId,
int $retryIndicator,
?string $expectedException = null): void
{
if ($expectedException !== null) {
$this->expectException($expectedException, $requestId, $retryIndicator);
}

new RequestConfig(
'apiKey',
'apiSecret',
'customerId',
false,
$requestId,
$retryIndicator
);
}

public function doTestSuccessfulResponse(string $responseClass): void
{
$this->successful = true;
Expand Down Expand Up @@ -87,7 +121,7 @@ private function getResponseMock(): MockObject

$response->expects($this->any())
->method('getStatusCode')
->will($this->returnValue($this->successful ? 200: 404));
->will($this->returnValue($this->successful ? 200 : 404));

if ($this->successful) {
$content = $this->getFakedApiResponse($this->successfulResponseClass);
Expand Down

0 comments on commit 625af7c

Please sign in to comment.