<?php
namespace App\BackOffice\ConfigurationBundle\Payment\Common\Message;
use Exception;
use App\BackOffice\ConfigurationBundle\Payment\Common\Message\ResponseInterface;
use ArrayAccess;
use JsonSerializable;
class AbstractResponse implements ResponseInterface, ArrayAccess, JsonSerializable
{
/**
* Gateway response codes
*
* Example:
* "103" => [
* 'isFatal' => false,
* 'parsedCode' => ResponseInterface::PARTNER_3DSECURE_REQUIRED,
* 'description' => '3DS required',
* 'is_split_billing' => false
* ],
*
* @const array
*/
protected const codes = [];
// These error codes will NOT be retried in the automatic retry process
protected const AUTOMATIC_RETRY_SKIP_CODES = [];
/**
* @var string $customerId
*
* Customer created in the psp
*/
protected $customerId;
/**
* Generic WR codes
* Code => Description
*/
protected $genericCodes = [
ResponseInterface::PARTNER_SUCCESSFUL_TRANSACTION => ResponseInterface::SUCCESSFUL_DESCRIPTION,
ResponseInterface::PARTNER_SUCCESSFUL_AUTH => ResponseInterface::PARTNER_SUCCESSFUL_AUTH,
ResponseInterface::PARTNER_SPLIT_CODE => ResponseInterface::PARTNER_SPLIT_CODE,
ResponseInterface::PARTNER_UNKNOWN_CODE => ResponseInterface::PARTNER_UNKNOWN_CODE,
ResponseInterface::PARTNER_REDIRECT_3DS_DECLINE => 'Transaction has been declined by the bank',
ResponseInterface::PARTNER_REDIRECT_3DS_DECLINE2 => 'Transaction has been declined by the bank',
ResponseInterface::PARTNER_REDIRECT_3DS_CANCEL => 'Transaction annulée',
ResponseInterface::PARTNER_REDIRECT_3DS_ERROR => 'Erreur lors du paiement',
ResponseInterface::PARTNER_DISABLED_FOR_AUTO => 'Mid disabled for auto processing',
];
/**
* The data contained in the response.
*
* @var mixed
*/
protected $data;
/**
* Shortcut to the response code contained in data
* @var mixed
*/
protected $responseCode;
/**
* Constructor
*
* @param mixed|null $data
*/
public function __construct($data)
{
$this->data = $data;
$this->responseCode = is_string($data) ? $data : null;
}
/**
* Is the response successful?
*
* @return bool
*/
public function isSuccessful(): bool
{
return false;
}
/**
* Is the response pending?
*
* @return boolean
*/
public function isPending(): bool
{
return false;
}
/**
* Is it a fatal error (non-retryable)?
*
* @throws \InvalidArgumentException
* @return bool
*/
public function isFatal(): bool
{
$this->checkCodeExists();
return $this->responseCode && (static::codes[$this->responseCode]['isFatal'] ?? false);
}
/**
* Does the response require a redirect?
*
* @return boolean
*/
public function isRedirect(): bool
{
return false;
}
/**
* Gets the redirect target url.
*
* @return string|null
*/
public function getRedirectUrl(): ?string
{
return null;
}
/**
* Get the required redirect method (GET or POST).
*
* @return string
*/
public function getRedirectMethod(): string
{
return 'GET';
}
/**
* Gets the redirect form data array, if the redirect method is POST.
*
* @return array
*/
public function getRedirectData(): array
{
return [];
}
/**
* Get the description associated with the response code
* @param string|null $code
* @return string
*/
public function getCodeDescription($code = null): string
{
$code = $code ?? $this->responseCode;
return $this->genericCodes[$code] ?? static::codes[$code]['description'] ??
ResponseInterface::NO_DESCRIPTION;
}
/**
* Response code
*
* @return mixed A response code from the payment gateway
*/
public function getCode()
{
return $this->responseCode;
}
/**
* Gateway Reference
*
* @return null|string A reference provided by the gateway to represent this transaction
*/
public function getTransactionReference()
{
return null;
}
/**
* Get the order ID.
*
* @return null|string
*/
public function getOrderId()
{
return null;
}
/**
* Get the response data.
*
* @return mixed
*/
public function getData()
{
return $this->data;
}
/**
* Get the token/wallet/registrationId
*
* @return string
*/
public function getToken(): string
{
return '';
}
/**
* Checks if code exists
*
* @param mixed $code
* @throws \InvalidArgumentException
*/
protected function checkCodeExists($code = null): void
{
$code = $code ?? $this->responseCode;
if ($code && !isset($this->genericCodes[$code]) && !isset(static::codes[$code])) {
throw new \InvalidArgumentException("Given error code ($code) doesnt exist " . get_class($this));
}
}
/**
* Returns response data as array
*
* @return array
*/
public function toArray(): array
{
if (is_array($this->data)) {
return $this->data;
} elseif (is_object($this->data)) {
return get_object_vars($this->data);
}
return [];
}
/**
* JsonSerializable interface implementation
*
* @return array|mixed
*/
public function jsonSerialize()
{
return $this->toArray();
}
/**
* ArrayAccess interface methods
*
* These methods are temporary to be compatible with old array access to the response result.
* This way we can use "$result->isFatal()" but also we can access the data the old way "$result["result"]["id"]"
* so we don't have to rewrite the full PSP code.
* They should be removed when not needed anymore when we have new PSP code that uses the response as an object.
*/
public function offsetExists($offset): bool
{
return isset($this->data[$offset]);
}
public function offsetGet($offset)
{
return $this->data[$offset];
}
public function offsetSet($offset, $value): void
{
$this->data[$offset] = $value;
}
public function offsetUnset($offset): void
{
unset($this->data[$offset]);
}
public function getBinInfo($bin)
{
return null;
}
public function getCustomerId(): string
{
return $this->customerId;
}
public function setCustomerId(string $customerId): void
{
$this->customerId = $customerId;
}
public static function getFatalCodeList(): array
{
$fatalCodes = array_filter(static::codes, function ($value, $key) {
return $value['isFatal'];
}, ARRAY_FILTER_USE_BOTH);
return $fatalCodes;
}
/**
* Can we retry automatically if we get this error code?
*
* @param mixed $errorCode
* @return bool
*/
public static function canBeAutoRetried($errorCode): bool
{
return !in_array($errorCode, static::AUTOMATIC_RETRY_SKIP_CODES ?? []);
}
/**
* Get the list of codes that should not be automatically retried
*
* @return array
*/
public function getSkipRetryCodeList(): array
{
$codes = static::AUTOMATIC_RETRY_SKIP_CODES ?? [];
$data = [];
foreach ($codes as $code) {
$data[$code] = $this->getCodeDescription($code);
}
return $data;
}
/**
* Get HTML to render
*
* @return string|null
*/
public function getRedirectHtml(): ?string
{
return null;
}
}