From 11c033f235d13722b625f0f5242b92a3c3528d1e Mon Sep 17 00:00:00 2001 From: Oleksandr Gribiennikov Date: Mon, 15 Apr 2024 17:28:25 +0300 Subject: [PATCH 01/23] Fix PHPStan errors in the code --- composer.json | 21 ++- src/WebToPay.php | 85 +++++++----- src/WebToPay/CallbackValidator.php | 46 +++---- src/WebToPay/Exception/Validation.php | 13 +- src/WebToPay/Factory.php | 137 +++++++------------ src/WebToPay/PaymentMethod.php | 133 ++++++++---------- src/WebToPay/PaymentMethodCountry.php | 115 +++++++--------- src/WebToPay/PaymentMethodGroup.php | 150 ++++++++++----------- src/WebToPay/PaymentMethodList.php | 111 +++++++-------- src/WebToPay/PaymentMethodListProvider.php | 41 ++---- src/WebToPay/RequestBuilder.php | 140 +++++++++---------- src/WebToPay/Sign/SS1SignChecker.php | 23 ++-- src/WebToPay/Sign/SS2SignChecker.php | 30 ++--- src/WebToPay/Sign/SignCheckerInterface.php | 10 +- src/WebToPay/SmsAnswerSender.php | 45 ++----- src/WebToPay/UrlBuilder.php | 78 +++++------ src/WebToPay/Util.php | 43 +++--- src/WebToPay/WebClient.php | 50 ++++--- src/WebToPayException.php | 42 +++--- src/includes.php | 2 +- 20 files changed, 599 insertions(+), 716 deletions(-) diff --git a/composer.json b/composer.json index 2714da5..e6e6ecb 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "webtopay/libwebtopay", "description": "PHP Library for Paysera payment gateway integration", + "version": "2.1.0", "license": "LGPL-3.0", "authors": [ { @@ -12,6 +13,24 @@ "classmap": ["src/"] }, "require": { - "php": "^5.5 || ^7.0 || ^8.0" + "php": ">=7.4", + "ext-simplexml": "*", + "ext-openssl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "friendsofphp/php-cs-fixer": "^3.38", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3" + }, + "scripts": { + "phpunit": "php -dpcov.enabled=1 ./vendor/phpunit/phpunit/phpunit tests --coverage-html=coverage" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/src/WebToPay.php b/src/WebToPay.php index d679ac9..d46b354 100644 --- a/src/WebToPay.php +++ b/src/WebToPay.php @@ -23,12 +23,11 @@ * @link http://www.webtopay.com/ */ - /** * Contains static methods for most used scenarios. */ -class WebToPay { - +class WebToPay +{ /** * WebToPay Library version. */ @@ -63,13 +62,14 @@ class WebToPay { * Possible keys: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request + * @param array $data Information about current payment request * - * @return array + * @return array * * @throws WebToPayException on data validation error */ - public static function buildRequest($data) { + public static function buildRequest(array $data): array + { if (!isset($data['sign_password']) || !isset($data['projectid'])) { throw new WebToPayException('sign_password or projectid is not provided'); } @@ -78,24 +78,25 @@ public static function buildRequest($data) { unset($data['sign_password']); unset($data['projectid']); - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRequest($data); } - /** * Builds request and redirects user to payment window with generated request data * * Possible array keys are described here: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request. + * @param array $data Information about current payment request. * @param boolean $exit if true, exits after sending Location header; default false * * @throws WebToPayException on data validation error */ - public static function redirectToPayment($data, $exit = false) { + public static function redirectToPayment(array $data, bool $exit = false): void + { if (!isset($data['sign_password']) || !isset($data['projectid'])) { throw new WebToPayException('sign_password or projectid is not provided'); } @@ -133,13 +134,14 @@ public static function redirectToPayment($data, $exit = false) { * keys are described here: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request + * @param array $data Information about current payment request * - * @return array + * @return array * * @throws WebToPayException on data validation error */ - public static function buildRepeatRequest($data) { + public static function buildRepeatRequest(array $data): array + { if (!isset($data['sign_password']) || !isset($data['projectid']) || !isset($data['orderid'])) { throw new WebToPayException('sign_password, projectid or orderid is not provided'); } @@ -149,16 +151,18 @@ public static function buildRepeatRequest($data) { $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRepeatRequest($orderId); } /** * Returns payment url. Argument is same as lang parameter in request data * - * @param string $language + * @param string $language * @return string $url */ - public static function getPaymentUrl($language = 'LIT') { + public static function getPaymentUrl(string $language = 'LIT'): string + { return (in_array($language, array('lt', 'lit', 'LIT'))) ? self::PAY_URL : self::PAYSERA_PAY_URL; @@ -177,18 +181,19 @@ public static function getPaymentUrl($language = 'LIT') { * * If response is not correct, WebToPayException will be raised. * - * @param array $query Response array - * @param array $userData + * @param array $query Response array + * @param array $userData * - * @return array + * @return array * * @throws WebToPayException * @deprecated use validateAndParseData() and check status code yourself */ - public static function checkResponse($query, $userData = array()) { - $projectId = isset($userData['projectid']) ? $userData['projectid'] : null; - $password = isset($userData['sign_password']) ? $userData['sign_password'] : null; - $logFile = isset($userData['log']) ? $userData['log'] : null; + public static function checkResponse(array $query, array $userData = []) + { + $projectId = isset($userData['projectid']) ? (int) $userData['projectid'] : null; + $password = isset($userData['sign_password']) ? (string) $userData['sign_password'] : null; + $logFile = isset($userData['log']) ? (string) $userData['log'] : null; try { $data = self::validateAndParseData($query, $projectId, $password); @@ -212,30 +217,34 @@ public static function checkResponse($query, $userData = array()) { /** * Parses request (query) data and validates its signature. * - * @param array $query usually $_GET - * @param integer $projectId - * @param string $password + * @param array $query usually $_GET + * @param int|null $projectId + * @param string|null $password * - * @return array + * @return array * * @throws WebToPayException + * @throws WebToPay_Exception_Callback + * @throws WebToPay_Exception_Configuration */ - public static function validateAndParseData(array $query, $projectId, $password) { + public static function validateAndParseData(array $query, ?int $projectId, ?string $password): array + { $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); $validator = $factory->getCallbackValidator(); - $data = $validator->validateAndParseData($query); - return $data; + + return $validator->validateAndParseData($query); } /** * Sends SMS answer * - * @param array $userData + * @param array $userData * * @throws WebToPayException * @throws WebToPay_Exception_Validation */ - public static function smsAnswer($userData) { + public static function smsAnswer(array $userData): void + { if (!isset($userData['id']) || !isset($userData['msg']) || !isset($userData['sign_password'])) { throw new WebToPay_Exception_Validation('id, msg and sign_password are required'); } @@ -260,10 +269,8 @@ public static function smsAnswer($userData) { } throw $e; } - } - /** * Gets available payment methods for project. Gets methods min and max amounts in specified currency. * @@ -275,8 +282,13 @@ public static function smsAnswer($userData) { * * @throws WebToPayException */ - public static function getPaymentMethodList($projectId, $amount, $currency = 'EUR') { + public static function getPaymentMethodList( + int $projectId, + float $amount, + string $currency = 'EUR' + ): WebToPay_PaymentMethodList { $factory = new WebToPay_Factory(array('projectId' => $projectId)); + return $factory->getPaymentMethodListProvider()->getPaymentMethodList($amount, $currency); } @@ -287,7 +299,8 @@ public static function getPaymentMethodList($projectId, $amount, $currency = 'EU * @param string $msg * @param string $logfile */ - protected static function log($type, $msg, $logfile) { + protected static function log(string $type, string $msg, string $logfile): void + { $fp = @fopen($logfile, 'a'); if (!$fp) { return; @@ -312,5 +325,3 @@ protected static function log($type, $msg, $logfile) { } } } - - diff --git a/src/WebToPay/CallbackValidator.php b/src/WebToPay/CallbackValidator.php index 65cb591..cfce357 100644 --- a/src/WebToPay/CallbackValidator.php +++ b/src/WebToPay/CallbackValidator.php @@ -3,27 +3,15 @@ /** * Parses and validates callbacks */ -class WebToPay_CallbackValidator { +class WebToPay_CallbackValidator +{ + protected WebToPay_Sign_SignCheckerInterface $signer; - /** - * @var WebToPay_Sign_SignCheckerInterface - */ - protected $signer; + protected WebToPay_Util $util; - /** - * @var WebToPay_Util - */ - protected $util; + protected int $projectId; - /** - * @var integer - */ - protected $projectId; - - /** - * @var string|null - */ - protected $password; + protected ?string $password; /** * Constructs object @@ -34,10 +22,10 @@ class WebToPay_CallbackValidator { * @param string|null $password */ public function __construct( - $projectId, + int $projectId, WebToPay_Sign_SignCheckerInterface $signer, WebToPay_Util $util, - $password = null + ?string $password = null ) { $this->signer = $signer; $this->util = $util; @@ -49,14 +37,15 @@ public function __construct( * Parses callback parameters from query parameters and checks if sign is correct. * Request has parameter "data", which is signed and holds all callback parameters * - * @param array $requestData + * @param array $requestData * - * @return array Parsed callback parameters + * @return array Parsed callback parameters * * @throws WebToPayException * @throws WebToPay_Exception_Callback */ - public function validateAndParseData(array $requestData) { + public function validateAndParseData(array $requestData): array + { if (!isset($requestData['data'])) { throw new WebToPay_Exception_Callback('"data" parameter not found'); } @@ -114,15 +103,16 @@ public function validateAndParseData(array $requestData) { /** * Checks data to have all the same parameters provided in expected array * - * @param array $data - * @param array $expected + * @param array $data + * @param array $expected * * @throws WebToPayException */ - public function checkExpectedFields(array $data, array $expected) { + public function checkExpectedFields(array $data, array $expected): void + { foreach ($expected as $key => $value) { - $passedValue = isset($data[$key]) ? $data[$key] : null; - if ($passedValue != $value) { + $passedValue = $data[$key] ?? null; + if ($passedValue !== $value) { throw new WebToPayException( sprintf('Field %s is not as expected (expected %s, got %s)', $key, $value, $passedValue) ); diff --git a/src/WebToPay/Exception/Validation.php b/src/WebToPay/Exception/Validation.php index ba3460b..5a72539 100644 --- a/src/WebToPay/Exception/Validation.php +++ b/src/WebToPay/Exception/Validation.php @@ -3,12 +3,17 @@ /** * Raised on validation error in passed data when building the request */ -class WebToPay_Exception_Validation extends WebToPayException { - - public function __construct($message, $code = 0, $field = null, Exception $previousException = null) { +class WebToPay_Exception_Validation extends WebToPayException +{ + public function __construct( + string $message, + int $code = 0, + ?string $field = null, + ?Exception $previousException = null + ) { parent::__construct($message, $code, $previousException); if ($field) { $this->setField($field); } } -} \ No newline at end of file +} diff --git a/src/WebToPay/Factory.php b/src/WebToPay/Factory.php index 1a784c0..a664d00 100644 --- a/src/WebToPay/Factory.php +++ b/src/WebToPay/Factory.php @@ -3,81 +3,53 @@ /** * Creates objects. Also caches to avoid creating several instances of same objects */ -class WebToPay_Factory { - +class WebToPay_Factory +{ const ENV_PRODUCTION = 'production'; const ENV_SANDBOX = 'sandbox'; /** - * @var array + * @var array */ - protected static $defaultConfiguration = array( - 'routes' => array( - self::ENV_PRODUCTION => array( + protected static array $defaultConfiguration = [ + 'routes' => [ + self::ENV_PRODUCTION => [ 'publicKey' => 'https://www.paysera.com/download/public.key', 'payment' => 'https://bank.paysera.com/pay/', 'paymentMethodList' => 'https://www.paysera.com/new/api/paymentMethods/', 'smsAnswer' => 'https://bank.paysera.com/psms/respond/', - ), - self::ENV_SANDBOX => array( + ], + self::ENV_SANDBOX => [ 'publicKey' => 'https://sandbox.paysera.com/download/public.key', 'payment' => 'https://sandbox.paysera.com/pay/', 'paymentMethodList' => 'https://sandbox.paysera.com/new/api/paymentMethods/', 'smsAnswer' => 'https://sandbox.paysera.com/psms/respond/', - ), - ) - ); + ], + ], + ]; - /** - * @var string - */ - protected $environment; + protected string $environment; /** - * @var array + * @var array */ - protected $configuration; + protected array $configuration; - /** - * @var WebToPay_WebClient - */ - protected $webClient = null; + protected ?WebToPay_WebClient $webClient = null; - /** - * @var WebToPay_CallbackValidator - */ - protected $callbackValidator = null; + protected ?WebToPay_CallbackValidator $callbackValidator = null; - /** - * @var WebToPay_RequestBuilder - */ - protected $requestBuilder = null; + protected ?WebToPay_RequestBuilder $requestBuilder = null; - /** - * @var WebToPay_Sign_SignCheckerInterface - */ - protected $signer = null; + protected ?WebToPay_Sign_SignCheckerInterface $signer = null; - /** - * @var WebToPay_SmsAnswerSender - */ - protected $smsAnswerSender = null; + protected ?WebToPay_SmsAnswerSender $smsAnswerSender = null; - /** - * @var WebToPay_PaymentMethodListProvider - */ - protected $paymentMethodListProvider = null; - - /** - * @var WebToPay_Util - */ - protected $util = null; + protected ?WebToPay_PaymentMethodListProvider $paymentMethodListProvider = null; - /** - * @var WebToPay_UrlBuilder - */ - protected $urlBuilder = null; + protected ?WebToPay_Util $util = null; + protected ?WebToPay_UrlBuilder $urlBuilder = null; /** * Constructs object. @@ -85,48 +57,45 @@ class WebToPay_Factory { * They are required only when some object being created needs them, * if they are not found at that moment - exception is thrown * - * @param array $configuration + * @param array $configuration */ - public function __construct(array $configuration = array()) { - + public function __construct(array $configuration = []) + { $this->configuration = array_merge(self::$defaultConfiguration, $configuration); $this->environment = self::ENV_PRODUCTION; } /** * If passed true the factory will use sandbox when constructing URLs - * - * @param $enableSandbox - * @return self */ - public function useSandbox($enableSandbox) + public function useSandbox(bool $enableSandbox): self { if ($enableSandbox) { $this->environment = self::ENV_SANDBOX; } else { $this->environment = self::ENV_PRODUCTION; } + return $this; } /** * Creates or gets callback validator instance * - * @return WebToPay_CallbackValidator - * * @throws WebToPay_Exception_Configuration */ - public function getCallbackValidator() { + public function getCallbackValidator(): WebToPay_CallbackValidator + { if ($this->callbackValidator === null) { if (!isset($this->configuration['projectId'])) { throw new WebToPay_Exception_Configuration('You have to provide project ID'); } $this->callbackValidator = new WebToPay_CallbackValidator( - $this->configuration['projectId'], + (int) $this->configuration['projectId'], $this->getSigner(), $this->getUtil(), - isset($this->configuration['password']) ? $this->configuration['password'] : null + $this->configuration['password'] ?? null ); } @@ -137,10 +106,9 @@ public function getCallbackValidator() { * Creates or gets request builder instance * * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_RequestBuilder */ - public function getRequestBuilder() { + public function getRequestBuilder(): WebToPay_RequestBuilder + { if ($this->requestBuilder === null) { if (!isset($this->configuration['password'])) { throw new WebToPay_Exception_Configuration('You have to provide project password to sign request'); @@ -155,19 +123,19 @@ public function getRequestBuilder() { $this->getUrlBuilder() ); } + return $this->requestBuilder; } - /** - * @return WebToPay_UrlBuilder - */ - public function getUrlBuilder() { + public function getUrlBuilder(): WebToPay_UrlBuilder + { if ($this->urlBuilder === null) { $this->urlBuilder = new WebToPay_UrlBuilder( $this->configuration, $this->environment ); } + return $this->urlBuilder; } @@ -175,10 +143,9 @@ public function getUrlBuilder() { * Creates or gets SMS answer sender instance * * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_SmsAnswerSender */ - public function getSmsAnswerSender() { + public function getSmsAnswerSender(): WebToPay_SmsAnswerSender + { if ($this->smsAnswerSender === null) { if (!isset($this->configuration['password'])) { throw new WebToPay_Exception_Configuration('You have to provide project password'); @@ -189,6 +156,7 @@ public function getSmsAnswerSender() { $this->getUrlBuilder() ); } + return $this->smsAnswerSender; } @@ -196,10 +164,9 @@ public function getSmsAnswerSender() { * Creates or gets payment list provider instance * * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_PaymentMethodListProvider */ - public function getPaymentMethodListProvider() { + public function getPaymentMethodListProvider(): WebToPay_PaymentMethodListProvider + { if ($this->paymentMethodListProvider === null) { if (!isset($this->configuration['projectId'])) { throw new WebToPay_Exception_Configuration('You have to provide project ID'); @@ -210,6 +177,7 @@ public function getPaymentMethodListProvider() { $this->getUrlBuilder() ); } + return $this->paymentMethodListProvider; } @@ -217,12 +185,10 @@ public function getPaymentMethodListProvider() { * Creates or gets signer instance. Chooses SS2 signer if openssl functions are available, SS1 in other case * * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_Sign_SignCheckerInterface - * * @throws WebToPayException */ - protected function getSigner() { + protected function getSigner(): WebToPay_Sign_SignCheckerInterface + { if ($this->signer === null) { if (function_exists('openssl_pkey_get_public')) { $webClient = $this->getWebClient(); @@ -240,6 +206,7 @@ protected function getSigner() { $this->signer = new WebToPay_Sign_SS1SignChecker($this->configuration['password']); } } + return $this->signer; } @@ -247,13 +214,13 @@ protected function getSigner() { * Creates or gets web client instance * * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_WebClient */ - protected function getWebClient() { + protected function getWebClient(): WebToPay_WebClient + { if ($this->webClient === null) { $this->webClient = new WebToPay_WebClient(); } + return $this->webClient; } @@ -261,13 +228,13 @@ protected function getWebClient() { * Creates or gets util instance * * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_Util */ - protected function getUtil() { + protected function getUtil():WebToPay_Util + { if ($this->util === null) { $this->util = new WebToPay_Util(); } + return $this->util; } } diff --git a/src/WebToPay/PaymentMethod.php b/src/WebToPay/PaymentMethod.php index 0354a48..1f55408 100644 --- a/src/WebToPay/PaymentMethod.php +++ b/src/WebToPay/PaymentMethod.php @@ -3,61 +3,65 @@ /** * Class to hold information about payment method */ -class WebToPay_PaymentMethod { +class WebToPay_PaymentMethod +{ /** * Assigned key for this payment method - * - * @var string */ - protected $key; + protected string $key; + + protected ?int $minAmount; + + protected ?int $maxAmount; + + protected ?string $currency; /** * Logo url list by language. Usually logo is same for all languages, but exceptions exist * - * @var array + * @var array */ - protected $logoList; + protected array $logoList; /** * Title list by language * - * @var array + * @var array */ - protected $titleTranslations; + protected array $titleTranslations; /** * Default language to use for titles - * - * @var string */ - protected $defaultLanguage; + protected string $defaultLanguage; - /** - * @var boolean - */ - protected $isIban; + protected bool $isIban; - /** - * @var string - */ - protected $baseCurrency; + protected ?string $baseCurrency; /** * Constructs object * - * @param string $key - * @param integer $minAmount - * @param integer $maxAmount - * @param string $currency - * @param array $logoList - * @param array $titleTranslations - * @param string $defaultLanguage - * @param bool $isIban - * @param string $baseCurrency + * @param string $key + * @param integer|null $minAmount + * @param integer|null $maxAmount + * @param string|null $currency + * @param array $logoList + * @param array $titleTranslations + * @param string $defaultLanguage + * @param bool $isIban + * @param string|null $baseCurrency */ public function __construct( - $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), - $defaultLanguage = 'lt', $isIban = false, $baseCurrency = null + string $key, + ?int $minAmount, + ?int $maxAmount, + ?string $currency, + array $logoList = [], + array $titleTranslations = [], + string $defaultLanguage = 'lt', + bool $isIban = false, + ?string $baseCurrency = null ) { $this->key = $key; $this->minAmount = $minAmount; @@ -73,43 +77,36 @@ public function __construct( /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethod */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethod + { $this->defaultLanguage = $language; + return $this; } /** * Gets default language for titles - * - * @return string */ - public function getDefaultLanguage() { + public function getDefaultLanguage(): string + { return $this->defaultLanguage; } /** * Get assigned payment method key - * - * @return string */ - public function getKey() { + public function getKey(): string + { return $this->key; } /** * Gets logo url for this payment method. Uses specified language or default one. * If logotype is not found for specified language, null is returned. - * - * @param string [Optional] $languageCode - * - * @return string|null */ - public function getLogoUrl($languageCode = null) { + public function getLogoUrl(?string $languageCode = null): ?string + { if ($languageCode !== null && isset($this->logoList[$languageCode])) { return $this->logoList[$languageCode]; } elseif (isset($this->logoList[$this->defaultLanguage])) { @@ -121,12 +118,9 @@ public function getLogoUrl($languageCode = null) { /** * Gets title for this payment method. Uses specified language or default one. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { + public function getTitle(?string $languageCode = null): string + { if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { return $this->titleTranslations[$languageCode]; } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { @@ -140,20 +134,17 @@ public function getTitle($languageCode = null) { * Checks if this payment method can be used for specified amount. * Throws exception if currency checked is not the one, for which payment method list was downloaded. * - * @param integer $amount - * @param string $currency - * - * @return boolean - * * @throws WebToPayException */ - public function isAvailableForAmount($amount, $currency) { + public function isAvailableForAmount(int $amount, string $currency): bool + { if ($this->currency !== $currency) { throw new WebToPayException( 'Currencies does not match. You have to get payment types for the currency you are checking. Given currency: ' . $currency . ', available currency: ' . $this->currency ); } + return ( ($this->minAmount === null || $amount >= $this->minAmount) && ($this->maxAmount === null || $amount <= $this->maxAmount) @@ -162,56 +153,48 @@ public function isAvailableForAmount($amount, $currency) { /** * Returns min amount for this payment method. If no min amount is specified, returns empty string. - * - * @return string */ - public function getMinAmountAsString() { + public function getMinAmountAsString(): string + { return $this->minAmount === null ? '' : ($this->minAmount . ' ' . $this->currency); } /** * Returns max amount for this payment method. If no max amount is specified, returns empty string. - * - * @return string */ - public function getMaxAmountAsString() { + public function getMaxAmountAsString(): string + { return $this->maxAmount === null ? '' : ($this->maxAmount . ' ' . $this->currency); } /** * Set if this method returns IBAN number after payment - * - * @param boolean $isIban */ - public function setIsIban($isIban) { + public function setIsIban(bool $isIban): void + { $this->isIban = $isIban == 1; } /** * Get if this method returns IBAN number after payment - * - * @return bool */ - public function isIban() { + public function isIban(): bool + { return $this->isIban; } /** * Setter of BaseCurrency - * - * @param string $baseCurrency */ - public function setBaseCurrency($baseCurrency) + public function setBaseCurrency(string $baseCurrency): void { $this->baseCurrency = $baseCurrency; } /** * Getter of BaseCurrency - * - * @return string */ - public function getBaseCurrency() + public function getBaseCurrency(): ?string { return $this->baseCurrency; } diff --git a/src/WebToPay/PaymentMethodCountry.php b/src/WebToPay/PaymentMethodCountry.php index 6e93dad..75ee952 100644 --- a/src/WebToPay/PaymentMethodCountry.php +++ b/src/WebToPay/PaymentMethodCountry.php @@ -3,72 +3,63 @@ /** * Payment method configuration for some country */ -class WebToPay_PaymentMethodCountry { - /** - * @var string - */ - protected $countryCode; +class WebToPay_PaymentMethodCountry +{ + protected string $countryCode; /** * Holds available payment types for this country * * @var WebToPay_PaymentMethodGroup[] */ - protected $groups; + protected array $groups; /** * Default language for titles - * - * @var string */ - protected $defaultLanguage; + protected string $defaultLanguage; /** * Translations array for this country. Holds associative array of country title by language codes. * - * @var array + * @var array */ - protected $titleTranslations; + protected array $titleTranslations; /** * Constructs object * * @param string $countryCode - * @param array $titleTranslations + * @param array $titleTranslations * @param string $defaultLanguage */ - public function __construct($countryCode, $titleTranslations, $defaultLanguage = 'lt') { + public function __construct(string $countryCode, array $titleTranslations, string $defaultLanguage = 'lt') { $this->countryCode = $countryCode; $this->defaultLanguage = $defaultLanguage; $this->titleTranslations = $titleTranslations; - $this->groups = array(); + $this->groups = []; } /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodCountry */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodCountry + { $this->defaultLanguage = $language; foreach ($this->groups as $group) { $group->setDefaultLanguage($language); } + return $this; } /** * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not * specified, uses default language, given to constructor. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { + public function getTitle(?string $languageCode = null): string + { if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { return $this->titleTranslations[$languageCode]; } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { @@ -80,19 +71,17 @@ public function getTitle($languageCode = null) { /** * Gets default language for titles - * - * @return string */ - public function getDefaultLanguage() { + public function getDefaultLanguage(): string + { return $this->defaultLanguage; } /** * Gets country code - * - * @return string */ - public function getCode() { + public function getCode(): string + { return $this->countryCode; } @@ -100,24 +89,18 @@ public function getCode() { * Adds new group to payment methods for this country. * If some other group was registered earlier with same key, overwrites it. * Returns given group - * - * @param WebToPay_PaymentMethodGroup $group - * - * @return WebToPay_PaymentMethodGroup */ - public function addGroup(WebToPay_PaymentMethodGroup $group) { + public function addGroup(WebToPay_PaymentMethodGroup $group): WebToPay_PaymentMethodGroup + { return $this->groups[$group->getKey()] = $group; } /** * Gets group object with specified group key. If no group with such key is found, returns null. - * - * @param string $groupKey - * - * @return null|WebToPay_PaymentMethodGroup */ - public function getGroup($groupKey) { - return isset($this->groups[$groupKey]) ? $this->groups[$groupKey] : null; + public function getGroup(string $groupKey): ?WebToPay_PaymentMethodGroup + { + return $this->groups[$groupKey] ?? null; } /** @@ -125,7 +108,8 @@ public function getGroup($groupKey) { * * @return WebToPay_PaymentMethodGroup[] */ - public function getGroups() { + public function getGroups(): array + { return $this->groups; } @@ -134,23 +118,21 @@ public function getGroups() { * * @return WebToPay_PaymentMethod[] */ - public function getPaymentMethods() { + public function getPaymentMethods(): array + { $paymentMethods = array(); foreach ($this->groups as $group) { $paymentMethods = array_merge($paymentMethods, $group->getPaymentMethods()); } + return $paymentMethods; } /** * Returns new country instance with only those payment methods, which are available for provided amount. - * - * @param integer $amount - * @param string $currency - * - * @return WebToPay_PaymentMethodCountry */ - public function filterForAmount($amount, $currency) { + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodCountry + { $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); foreach ($this->getGroups() as $group) { $group = $group->filterForAmount($amount, $currency); @@ -158,42 +140,44 @@ public function filterForAmount($amount, $currency) { $country->addGroup($group); } } + return $country; } /** * Returns new country instance with only those payment methods, which are returns or not iban number after payment - * - * @param boolean $isIban - * - * @return WebToPay_PaymentMethodCountry */ - public function filterForIban($isIban = true) { - $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); + public function filterForIban(bool $isIban = true): WebToPay_PaymentMethodCountry + { + $country = new WebToPay_PaymentMethodCountry( + $this->countryCode, + $this->titleTranslations, + $this->defaultLanguage + ); + foreach ($this->getGroups() as $group) { $group = $group->filterForIban($isIban); if (!$group->isEmpty()) { $country->addGroup($group); } } + return $country; } /** * Returns whether this country has no groups - * - * @return boolean */ - public function isEmpty() { + public function isEmpty(): bool + { return count($this->groups) === 0; } /** * Loads groups from given XML node - * - * @param SimpleXMLElement $countryNode */ - public function fromXmlNode($countryNode) { + public function fromXmlNode(SimpleXMLElement $countryNode): void + { foreach ($countryNode->payment_group as $groupNode) { $key = (string) $groupNode->attributes()->key; $titleTranslations = array(); @@ -206,13 +190,14 @@ public function fromXmlNode($countryNode) { /** * Method to create new group instances. Overwrite if you have to use some other group subtype. - * + * * @param string $groupKey - * @param array $translations + * @param array $translations * * @return WebToPay_PaymentMethodGroup */ - protected function createGroup($groupKey, array $translations = array()) { + protected function createGroup(string $groupKey, array $translations = []): WebToPay_PaymentMethodGroup + { return new WebToPay_PaymentMethodGroup($groupKey, $translations, $this->defaultLanguage); } -} \ No newline at end of file +} diff --git a/src/WebToPay/PaymentMethodGroup.php b/src/WebToPay/PaymentMethodGroup.php index 4f0330f..94f0b2e 100644 --- a/src/WebToPay/PaymentMethodGroup.php +++ b/src/WebToPay/PaymentMethodGroup.php @@ -4,83 +4,75 @@ * Wrapper class to group payment methods. Each country can have several payment method groups, each of them * have one or more payment methods. */ -class WebToPay_PaymentMethodGroup { +class WebToPay_PaymentMethodGroup +{ /** * Some unique (in the scope of country) key for this group - * - * @var string */ - protected $groupKey; + protected string $groupKey; /** * Translations array for this group. Holds associative array of group title by country codes. * - * @var array + * @var array */ - protected $translations; + protected array $translations; /** * Holds actual payment methods * * @var WebToPay_PaymentMethod[] */ - protected $paymentMethods; + protected array $paymentMethods; /** * Default language for titles - * - * @var string */ - protected $defaultLanguage; + protected string $defaultLanguage; /** * Constructs object * * @param string $groupKey - * @param array $translations + * @param array $translations * @param string $defaultLanguage */ - public function __construct($groupKey, array $translations = array(), $defaultLanguage = 'lt') { + public function __construct(string $groupKey, array $translations = [], string $defaultLanguage = 'lt') + { $this->groupKey = $groupKey; $this->translations = $translations; $this->defaultLanguage = $defaultLanguage; - $this->paymentMethods = array(); + $this->paymentMethods = []; } /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodGroup */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodGroup + { $this->defaultLanguage = $language; foreach ($this->paymentMethods as $paymentMethod) { $paymentMethod->setDefaultLanguage($language); } + return $this; } /** * Gets default language for titles - * - * @return string */ - public function getDefaultLanguage() { + public function getDefaultLanguage(): string + { return $this->defaultLanguage; } /** * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not * specified, uses default language, given to constructor. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { + public function getTitle(?string $languageCode = null): string + { if ($languageCode !== null && isset($this->translations[$languageCode])) { return $this->translations[$languageCode]; } elseif (isset($this->translations[$this->defaultLanguage])) { @@ -92,10 +84,9 @@ public function getTitle($languageCode = null) { /** * Returns group key - * - * @return string */ - public function getKey() { + public function getKey(): string + { return $this->groupKey; } @@ -104,7 +95,8 @@ public function getKey() { * * @return WebToPay_PaymentMethod[] */ - public function getPaymentMethods() { + public function getPaymentMethods(): array + { return $this->paymentMethods; } @@ -118,81 +110,76 @@ public function getPaymentMethods() { * * @return WebToPay_PaymentMethod */ - public function addPaymentMethod(WebToPay_PaymentMethod $paymentMethod) { + public function addPaymentMethod(WebToPay_PaymentMethod $paymentMethod): WebToPay_PaymentMethod + { return $this->paymentMethods[$paymentMethod->getKey()] = $paymentMethod; } /** * Gets payment method object with key. If no payment method with such key is found, returns null. - * - * @param string $key - * - * @return null|WebToPay_PaymentMethod */ - public function getPaymentMethod($key) { + public function getPaymentMethod(string $key): ?WebToPay_PaymentMethod + { return isset($this->paymentMethods[$key]) ? $this->paymentMethods[$key] : null; } /** * Returns new group instance with only those payment methods, which are available for provided amount. * - * @param integer $amount - * @param string $currency - * - * @return WebToPay_PaymentMethodGroup + * @throws WebToPayException */ - public function filterForAmount($amount, $currency) { + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodGroup + { $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); foreach ($this->getPaymentMethods() as $paymentMethod) { if ($paymentMethod->isAvailableForAmount($amount, $currency)) { $group->addPaymentMethod($paymentMethod); } } + return $group; } /** * Returns new country instance with only those payment methods, which are returns or not iban number after payment - * - * @param boolean $isIban - * - * @return WebToPay_PaymentMethodGroup */ - public function filterForIban($isIban = true) { + public function filterForIban(bool $isIban = true): WebToPay_PaymentMethodGroup + { $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); foreach ($this->getPaymentMethods() as $paymentMethod) { if ($paymentMethod->isIban() == $isIban) { $group->addPaymentMethod($paymentMethod); } } + return $group; } /** * Returns whether this group has no payment methods * - * @return boolean + * @return bool */ - public function isEmpty() { + public function isEmpty(): bool + { return count($this->paymentMethods) === 0; } /** * Loads payment methods from given XML node - * - * @param SimpleXMLElement $groupNode */ - public function fromXmlNode($groupNode) { + public function fromXmlNode(SimpleXMLElement $groupNode): void + { foreach ($groupNode->payment_type as $paymentTypeNode) { - $key = (string) $paymentTypeNode->attributes()->key; - $titleTranslations = array(); + $key = (string)$paymentTypeNode->attributes()->key; + $titleTranslations = []; foreach ($paymentTypeNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + $titleTranslations[(string)$titleNode->attributes()->language] = (string)$titleNode; } - $logoTranslations = array(); + $logoTranslations = []; foreach ($paymentTypeNode->logo_url as $logoNode) { - if ((string) $logoNode !== '') { - $logoTranslations[(string) $logoNode->attributes()->language] = (string) $logoNode; + if ((string)$logoNode !== '') { + $logoTranslations[(string)$logoNode->attributes()->language] = (string)$logoNode; } } $minAmount = null; @@ -201,19 +188,19 @@ public function fromXmlNode($groupNode) { $isIban = false; $baseCurrency = null; if (isset($paymentTypeNode->min)) { - $minAmount = (int) $paymentTypeNode->min->attributes()->amount; - $currency = (string) $paymentTypeNode->min->attributes()->currency; + $minAmount = (int)$paymentTypeNode->min->attributes()->amount; + $currency = (string)$paymentTypeNode->min->attributes()->currency; } if (isset($paymentTypeNode->max)) { - $maxAmount = (int) $paymentTypeNode->max->attributes()->amount; - $currency = (string) $paymentTypeNode->max->attributes()->currency; + $maxAmount = (int)$paymentTypeNode->max->attributes()->amount; + $currency = (string)$paymentTypeNode->max->attributes()->currency; } if (isset($paymentTypeNode->is_iban)) { - $isIban = (int) $paymentTypeNode->is_iban; + $isIban = (bool)$paymentTypeNode->is_iban; } if (isset($paymentTypeNode->base_currency)) { - $baseCurrency = (string) $paymentTypeNode->base_currency; + $baseCurrency = (string)$paymentTypeNode->base_currency; } $this->addPaymentMethod($this->createPaymentMethod( $key, $minAmount, $maxAmount, $currency, $logoTranslations, $titleTranslations, $isIban, $baseCurrency @@ -225,23 +212,36 @@ public function fromXmlNode($groupNode) { * Method to create new payment method instances. Overwrite if you have to use some other subclass. * * @param string $key - * @param integer $minAmount - * @param integer $maxAmount - * @param string $currency - * @param array $logoList - * @param array $titleTranslations + * @param int|null $minAmount + * @param int|null $maxAmount + * @param string|null $currency + * @param array $logoList + * @param array $titleTranslations * @param bool $isIban - * @param null $baseCurrency + * @param mixed $baseCurrency * * @return WebToPay_PaymentMethod */ protected function createPaymentMethod( - $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), - $isIban = false, $baseCurrency = null - ) { + string $key, + ?int $minAmount, + ?int $maxAmount, + ?string $currency, + array $logoList = [], + array $titleTranslations = [], + bool $isIban = false, + $baseCurrency = null + ): WebToPay_PaymentMethod { return new WebToPay_PaymentMethod( - $key, $minAmount, $maxAmount, $currency, $logoList, $titleTranslations, $this->defaultLanguage, - $isIban, $baseCurrency + $key, + $minAmount, + $maxAmount, + $currency, + $logoList, + $titleTranslations, + $this->defaultLanguage, + $isIban, + $baseCurrency ); } -} \ No newline at end of file +} diff --git a/src/WebToPay/PaymentMethodList.php b/src/WebToPay/PaymentMethodList.php index 4612a1a..e817fe8 100644 --- a/src/WebToPay/PaymentMethodList.php +++ b/src/WebToPay/PaymentMethodList.php @@ -3,53 +3,47 @@ /** * Class with all information about available payment methods for some project, optionally filtered by some amount. */ -class WebToPay_PaymentMethodList { +class WebToPay_PaymentMethodList +{ /** * Holds available payment countries * * @var WebToPay_PaymentMethodCountry[] */ - protected $countries; + protected array $countries; /** * Default language for titles - * - * @var string */ - protected $defaultLanguage; + protected string $defaultLanguage; /** * Project ID, to which this method list is valid - * - * @var integer */ - protected $projectId; + protected int $projectId; /** * Currency for min and max amounts in this list - * - * @var string */ - protected $currency; + protected string $currency; /** * If this list is filtered for some amount, this field defines it - * - * @var integer */ - protected $amount; + protected ?int $amount; /** * Constructs object * - * @param integer $projectId - * @param string $currency currency for min and max amounts in this list - * @param string $defaultLanguage - * @param integer $amount null if this list is not filtered by amount + * @param int $projectId + * @param string $currency currency for min and max amounts in this list + * @param string $defaultLanguage + * @param int|null $amount null if this list is not filtered by amount */ - public function __construct($projectId, $currency, $defaultLanguage = 'lt', $amount = null) { + public function __construct(int $projectId, string $currency, string $defaultLanguage = 'lt', ?int $amount = null) + { $this->projectId = $projectId; - $this->countries = array(); + $this->countries = []; $this->defaultLanguage = $defaultLanguage; $this->currency = $currency; $this->amount = $amount; @@ -58,52 +52,46 @@ public function __construct($projectId, $currency, $defaultLanguage = 'lt', $amo /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodList */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodList + { $this->defaultLanguage = $language; foreach ($this->countries as $country) { $country->setDefaultLanguage($language); } + return $this; } /** * Gets default language for titles - * - * @return string */ - public function getDefaultLanguage() { + public function getDefaultLanguage(): string + { return $this->defaultLanguage; } /** * Gets project ID for this payment method list - * - * @return integer */ - public function getProjectId() { + public function getProjectId(): int + { return $this->projectId; } /** * Gets currency for min and max amounts in this list - * - * @return string */ - public function getCurrency() { + public function getCurrency(): string + { return $this->currency; } /** * Gets whether this list is already filtered for some amount - * - * @return boolean */ - public function isFiltered() { + public function isFiltered(): bool + { return $this->amount !== null; } @@ -112,30 +100,25 @@ public function isFiltered() { * * @return WebToPay_PaymentMethodCountry[] */ - public function getCountries() { + public function getCountries(): array + { return $this->countries; } /** * Adds new country to payment methods. If some other country with same code was registered earlier, overwrites it. * Returns added country instance - * - * @param WebToPay_PaymentMethodCountry $country - * - * @return WebToPay_PaymentMethodCountry */ - public function addCountry(WebToPay_PaymentMethodCountry $country) { + public function addCountry(WebToPay_PaymentMethodCountry $country): WebToPay_PaymentMethodCountry + { return $this->countries[$country->getCode()] = $country; } /** * Gets country object with specified country code. If no country with such country code is found, returns null. - * - * @param string $countryCode - * - * @return null|WebToPay_PaymentMethodCountry */ - public function getCountry($countryCode) { + public function getCountry(string $countryCode): ?WebToPay_PaymentMethodCountry + { return isset($this->countries[$countryCode]) ? $this->countries[$countryCode] : null; } @@ -144,17 +127,16 @@ public function getCountry($countryCode) { * amount. * Returns itself, if list is already filtered and filter amount matches the given one. * - * @param integer $amount - * @param string $currency - * - * @return WebToPay_PaymentMethodList - * * @throws WebToPayException if this list is already filtered and not for provided amount */ - public function filterForAmount($amount, $currency) { + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodList + { if ($currency !== $this->currency) { throw new WebToPayException( - 'Currencies do not match. Given currency: ' . $currency . ', currency in list: ' . $this->currency + 'Currencies do not match. Given currency: ' + . $currency + . ', currency in list: ' + . $this->currency ); } if ($this->isFiltered()) { @@ -171,22 +153,22 @@ public function filterForAmount($amount, $currency) { $list->addCountry($country); } } + return $list; } } /** * Loads countries from given XML node - * - * @param SimpleXMLElement $xmlNode */ - public function fromXmlNode($xmlNode) { + public function fromXmlNode(SimpleXMLElement $xmlNode): void + { foreach ($xmlNode->country as $countryNode) { - $titleTranslations = array(); + $titleTranslations = []; foreach ($countryNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + $titleTranslations[(string)$titleNode->attributes()->language] = (string)$titleNode; } - $this->addCountry($this->createCountry((string) $countryNode->attributes()->code, $titleTranslations)) + $this->addCountry($this->createCountry((string)$countryNode->attributes()->code, $titleTranslations)) ->fromXmlNode($countryNode); } } @@ -195,11 +177,12 @@ public function fromXmlNode($xmlNode) { * Method to create new country instances. Overwrite if you have to use some other country subtype. * * @param string $countryCode - * @param array $titleTranslations + * @param array $titleTranslations * * @return WebToPay_PaymentMethodCountry */ - protected function createCountry($countryCode, array $titleTranslations = array()) { + protected function createCountry(string $countryCode, array $titleTranslations = []): WebToPay_PaymentMethodCountry + { return new WebToPay_PaymentMethodCountry($countryCode, $titleTranslations, $this->defaultLanguage); } -} \ No newline at end of file +} diff --git a/src/WebToPay/PaymentMethodListProvider.php b/src/WebToPay/PaymentMethodListProvider.php index 78d120d..f32fa8e 100644 --- a/src/WebToPay/PaymentMethodListProvider.php +++ b/src/WebToPay/PaymentMethodListProvider.php @@ -4,43 +4,31 @@ * Loads data about payment methods and constructs payment method list object from that data * You need SimpleXML support to use this feature */ -class WebToPay_PaymentMethodListProvider { +class WebToPay_PaymentMethodListProvider +{ + protected int $projectId; - /** - * @var integer - */ - protected $projectId; - - /** - * @var WebToPay_WebClient - */ - protected $webClient; + protected WebToPay_WebClient $webClient; /** * Holds constructed method lists by currency * * @var WebToPay_PaymentMethodList[] */ - protected $methodListCache = array(); + protected array $methodListCache = []; /** * Builds various request URLs - * - * @var WebToPay_UrlBuilder $urlBuilder */ - protected $urlBuilder; + protected WebToPay_UrlBuilder $urlBuilder; /** * Constructs object * - * @param integer $projectId - * @param WebToPay_WebClient $webClient - * @param WebToPay_UrlBuilder $urlBuilder - * * @throws WebToPayException if SimpleXML is not available */ public function __construct( - $projectId, + int $projectId, WebToPay_WebClient $webClient, WebToPay_UrlBuilder $urlBuilder ) @@ -57,16 +45,14 @@ public function __construct( /** * Gets payment method list for specified currency * - * @param float $amount - * @param string $currency - * - * @return WebToPay_PaymentMethodList - * * @throws WebToPayException */ - public function getPaymentMethodList($amount, $currency) { + public function getPaymentMethodList(float $amount, string $currency): WebToPay_PaymentMethodList + { if (!isset($this->methodListCache[$currency])) { - $xmlAsString = $this->webClient->get($this->urlBuilder->buildForPaymentsMethodList($this->projectId, $amount, $currency)); + $xmlAsString = $this->webClient->get( + $this->urlBuilder->buildForPaymentsMethodList($this->projectId, (string) $amount, $currency) + ); $useInternalErrors = libxml_use_internal_errors(false); $rootNode = simplexml_load_string($xmlAsString); libxml_clear_errors(); @@ -78,6 +64,7 @@ public function getPaymentMethodList($amount, $currency) { $methodList->fromXmlNode($rootNode); $this->methodListCache[$currency] = $methodList; } + return $this->methodListCache[$currency]; } -} \ No newline at end of file +} diff --git a/src/WebToPay/RequestBuilder.php b/src/WebToPay/RequestBuilder.php index 7c73d91..11e86b0 100644 --- a/src/WebToPay/RequestBuilder.php +++ b/src/WebToPay/RequestBuilder.php @@ -3,44 +3,25 @@ /** * Builds and signs requests */ -class WebToPay_RequestBuilder { +class WebToPay_RequestBuilder +{ + protected string $projectPassword; - /** - * @var string - */ - protected $projectPassword; - - /** - * @var WebToPay_Util - */ - protected $util; + protected WebToPay_Util $util; - /** - * @var integer - */ - protected $projectId; + protected int $projectId; - - /** - * @var WebToPay_UrlBuilder $urlBuilder - */ - protected $urlBuilder; + protected WebToPay_UrlBuilder $urlBuilder; /** * Constructs object - * - * @param integer $projectId - * @param string $projectPassword - * @param WebToPay_Util $util - * @param WebToPay_UrlBuilder $urlBuilder */ public function __construct( - $projectId, - $projectPassword, + int $projectId, + string $projectPassword, WebToPay_Util $util, WebToPay_UrlBuilder $urlBuilder - ) - { + ) { $this->projectId = $projectId; $this->projectPassword = $projectPassword; $this->util = $util; @@ -53,29 +34,34 @@ public function __construct( * This method checks all given data and generates correct request data * array or raises WebToPayException on failure. * - * @param array $data information about current payment request + * @param array $data information about current payment request * - * @return array + * @return array * * @throws WebToPayException */ - public function buildRequest($data) { + public function buildRequest(array $data): array + { $this->validateRequest($data, self::getRequestSpec()); $data['version'] = WebToPay::VERSION; $data['projectid'] = $this->projectId; unset($data['repeat_request']); + return $this->createRequest($data); } /** * Builds the full request url (including the protocol and the domain) * - * @param array $data + * @param array $data * @return string + * @throws WebToPayException */ - public function buildRequestUrlFromData($data) { - $language = isset($data['lang']) ? $data['lang'] : null; + public function buildRequestUrlFromData(array $data): string + { + $language = $data['lang'] ?? null; $request = $this->buildRequest($data); + return $this->urlBuilder->buildForRequest($request, $language); } @@ -87,40 +73,45 @@ public function buildRequestUrlFromData($data) { * * @param string $orderId order id of repeated request * - * @return array + * @return array * * @throws WebToPayException */ - public function buildRepeatRequest($orderId) { + public function buildRepeatRequest(string $orderId): array + { $data['orderid'] = $orderId; $data['version'] = WebToPay::VERSION; $data['projectid'] = $this->projectId; $data['repeat_request'] = '1'; + return $this->createRequest($data); } /** * Builds the full request url for a repeated request (including the protocol and the domain) * - * @param string $orderId order id of repeated request - * @return string + * @throws WebToPayException */ - public function buildRepeatRequestUrlFromOrderId($orderId) { + public function buildRepeatRequestUrlFromOrderId(string $orderId): string + { $request = $this->buildRepeatRequest($orderId); + return $this->urlBuilder->buildForRequest($request); } /** * Checks data to be valid by passed specification * - * @param array $data - * @param array $specs + * @param array $data + * @param array $specs * * @throws WebToPay_Exception_Validation */ - protected function validateRequest($data, $specs) { + protected function validateRequest(array $data, array $specs): void + { foreach ($specs as $spec) { - list($name, $maxlen, $required, $regexp) = $spec; + [$name, $maxlen, $required, $regexp] = $spec; + if ($required && !isset($data[$name])) { throw new WebToPay_Exception_Validation( sprintf("'%s' is required but missing.", $name), @@ -153,16 +144,18 @@ protected function validateRequest($data, $specs) { /** * Makes request data array from parameters, also generates signature * - * @param array $request + * @param array $request * - * @return array + * @return array */ - protected function createRequest(array $request) { + protected function createRequest(array $request): array + { $data = $this->util->encodeSafeUrlBase64(http_build_query($request, '', '&')); - return array( + + return [ 'data' => $data, 'sign' => md5($data . $this->projectPassword), - ); + ]; } /** @@ -174,30 +167,31 @@ protected function createRequest(array $request) { * required – is this item is required * regexp – regexp to test item value * - * @return array + * @return array> */ - protected static function getRequestSpec() { - return array( - array('orderid', 40, true, ''), - array('accepturl', 255, true, ''), - array('cancelurl', 255, true, ''), - array('callbackurl', 255, true, ''), - array('lang', 3, false, '/^[a-z]{3}$/i'), - array('amount', 11, false, '/^\d+$/'), - array('currency', 3, false, '/^[a-z]{3}$/i'), - array('payment', 20, false, ''), - array('country', 2, false, '/^[a-z_]{2}$/i'), - array('paytext', 255, false, ''), - array('p_firstname', 255, false, ''), - array('p_lastname', 255, false, ''), - array('p_email', 255, false, ''), - array('p_street', 255, false, ''), - array('p_city', 255, false, ''), - array('p_state', 255, false, ''), - array('p_zip', 20, false, ''), - array('p_countrycode', 2, false, '/^[a-z]{2}$/i'), - array('test', 1, false, '/^[01]$/'), - array('time_limit', 19, false, '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/'), - ); + protected static function getRequestSpec(): array + { + return [ + ['orderid', 40, true, ''], + ['accepturl', 255, true, ''], + ['cancelurl', 255, true, ''], + ['callbackurl', 255, true, ''], + ['lang', 3, false, '/^[a-z]{3}$/i'], + ['amount', 11, false, '/^\d+$/'], + ['currency', 3, false, '/^[a-z]{3}$/i'], + ['payment', 20, false, ''], + ['country', 2, false, '/^[a-z_]{2}$/i'], + ['paytext', 255, false, ''], + ['p_firstname', 255, false, ''], + ['p_lastname', 255, false, ''], + ['p_email', 255, false, ''], + ['p_street', 255, false, ''], + ['p_city', 255, false, ''], + ['p_state', 255, false, ''], + ['p_zip', 20, false, ''], + ['p_countrycode', 2, false, '/^[a-z]{2}$/i'], + ['test', 1, false, '/^[01]$/'], + ['time_limit', 19, false, '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/'], + ]; } -} \ No newline at end of file +} diff --git a/src/WebToPay/Sign/SS1SignChecker.php b/src/WebToPay/Sign/SS1SignChecker.php index 1639483..30cb964 100644 --- a/src/WebToPay/Sign/SS1SignChecker.php +++ b/src/WebToPay/Sign/SS1SignChecker.php @@ -3,36 +3,33 @@ /** * Sign checker which checks SS1 signature. SS1 does not depend on SSL functions */ -class WebToPay_Sign_SS1SignChecker implements WebToPay_Sign_SignCheckerInterface { - - /** - * @var string - */ - protected $projectPassword; +class WebToPay_Sign_SS1SignChecker implements WebToPay_Sign_SignCheckerInterface +{ + protected string $projectPassword; /** * Constructs object - * - * @param string $projectPassword */ - public function __construct($projectPassword) { + public function __construct(string $projectPassword) + { $this->projectPassword = $projectPassword; } /** * Check for SS1, which is not depend on openssl functions. * - * @param array $request + * @param array $request * - * @return boolean + * @return bool * * @throws WebToPay_Exception_Callback */ - public function checkSign(array $request) { + public function checkSign(array $request): bool + { if (!isset($request['data']) || !isset($request['ss1'])) { throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); } return md5($request['data'] . $this->projectPassword) === $request['ss1']; } -} \ No newline at end of file +} diff --git a/src/WebToPay/Sign/SS2SignChecker.php b/src/WebToPay/Sign/SS2SignChecker.php index ba94567..fc34aa1 100644 --- a/src/WebToPay/Sign/SS2SignChecker.php +++ b/src/WebToPay/Sign/SS2SignChecker.php @@ -3,25 +3,17 @@ /** * Checks SS2 signature. Depends on SSL functions */ -class WebToPay_Sign_SS2SignChecker implements WebToPay_Sign_SignCheckerInterface { +class WebToPay_Sign_SS2SignChecker implements WebToPay_Sign_SignCheckerInterface +{ + protected string $publicKey; - /** - * @var string - */ - protected $publicKey; - - /** - * @var WebToPay_Util - */ - protected $util; + protected WebToPay_Util $util; /** * Constructs object - * - * @param string $publicKey - * @param WebToPay_Util $util */ - public function __construct($publicKey, WebToPay_Util $util) { + public function __construct(string $publicKey, WebToPay_Util $util) + { $this->publicKey = $publicKey; $this->util = $util; } @@ -29,19 +21,21 @@ public function __construct($publicKey, WebToPay_Util $util) { /** * Checks signature * - * @param array $request + * @param array $request * - * @return boolean + * @return bool * * @throws WebToPay_Exception_Callback */ - public function checkSign(array $request) { + public function checkSign(array $request): bool + { if (!isset($request['data']) || !isset($request['ss2'])) { throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); } $ss2 = $this->util->decodeSafeUrlBase64($request['ss2']); $ok = openssl_verify($request['data'], $ss2, $this->publicKey); + return $ok === 1; } -} \ No newline at end of file +} diff --git a/src/WebToPay/Sign/SignCheckerInterface.php b/src/WebToPay/Sign/SignCheckerInterface.php index a710e15..ef30004 100644 --- a/src/WebToPay/Sign/SignCheckerInterface.php +++ b/src/WebToPay/Sign/SignCheckerInterface.php @@ -3,14 +3,14 @@ /** * Interface for sign checker */ -interface WebToPay_Sign_SignCheckerInterface { - +interface WebToPay_Sign_SignCheckerInterface +{ /** * Checks whether request is signed properly * - * @param array $request + * @param array $request * * @return boolean */ - public function checkSign(array $request); -} \ No newline at end of file + public function checkSign(array $request): bool; +} diff --git a/src/WebToPay/SmsAnswerSender.php b/src/WebToPay/SmsAnswerSender.php index acc4c51..9e60dd6 100644 --- a/src/WebToPay/SmsAnswerSender.php +++ b/src/WebToPay/SmsAnswerSender.php @@ -3,55 +3,38 @@ /** * Sends answer to SMS payment if it was not provided with response to callback */ -class WebToPay_SmsAnswerSender { +class WebToPay_SmsAnswerSender +{ + protected string $password; - /** - * @var string - */ - protected $password; - - /** - * @var WebToPay_WebClient - */ - protected $webClient; + protected WebToPay_WebClient $webClient; - /** - * @var WebToPay_UrlBuilder $urlBuilder - */ - protected $urlBuilder; + protected WebToPay_UrlBuilder $urlBuilder; /** * Constructs object - * - * @param string $password - * @param WebToPay_WebClient $webClient - * @param WebToPay_UrlBuilder $urlBuilder */ - public function __construct( - $password, - WebToPay_WebClient $webClient, - WebToPay_UrlBuilder $urlBuilder - ) { + public function __construct(string $password, WebToPay_WebClient $webClient, WebToPay_UrlBuilder $urlBuilder) + { $this->password = $password; $this->webClient = $webClient; $this->urlBuilder = $urlBuilder; } /** - * Sends answer by sms ID get from callback. Answer can be send only if it was not provided - * when responding to callback - * - * @param integer $smsId - * @param string $text + * Sends answer by sms ID get from callback. Answer can be sent only if it was not provided + * when responding to the callback * * @throws WebToPayException */ - public function sendAnswer($smsId, $text) { - $content = $this->webClient->get($this->urlBuilder->buildForSmsAnswer(), array( + public function sendAnswer(int $smsId, string $text): void + { + $content = $this->webClient->get($this->urlBuilder->buildForSmsAnswer(), [ 'id' => $smsId, 'msg' => $text, 'transaction' => md5($this->password . '|' . $smsId), - )); + ]); + if (strpos($content, 'OK') !== 0) { throw new WebToPayException( sprintf('Error: %s', $content), diff --git a/src/WebToPay/UrlBuilder.php b/src/WebToPay/UrlBuilder.php index 7e68f6b..fa32b24 100644 --- a/src/WebToPay/UrlBuilder.php +++ b/src/WebToPay/UrlBuilder.php @@ -1,35 +1,31 @@ */ - protected $configuration = array(); + protected array $configuration; - /** - * @var string - */ - protected $environment; + protected string $environment; /** - * @var array + * @var array */ - protected $environmentSettings; + protected array $environmentSettings; /** - * @param array $configuration + * @param array $configuration * @param string $environment */ - function __construct($configuration, $environment) + public function __construct(array $configuration, string $environment) { $this->configuration = $configuration; $this->environment = $environment; @@ -39,65 +35,63 @@ function __construct($configuration, $environment) /** * Builds a complete request URL based on the provided parameters * - * @param $request - * @param null $language + * @param array $request + * @param string|null $language + * * @return string */ - public function buildForRequest($request, $language = null) { + public function buildForRequest(array $request, ?string $language = null): string + { return $this->createUrlFromRequestAndLanguage($request); } /** * Builds a complete URL for payment list API - * - * @param int $projectId - * @param string $amount - * @param string $currency - * @return string */ - public function buildForPaymentsMethodList($projectId, $amount, $currency) { + public function buildForPaymentsMethodList(int $projectId, string $amount, string $currency): string + { $route = $this->environmentSettings['paymentMethodList']; + return $route . $projectId . '/currency:' . $currency . '/amount:' . $amount; } /** * Builds a complete URL for Sms Answer - * - * @return string */ - public function buildForSmsAnswer() { - $route = $this->environmentSettings['smsAnswer']; - return $route; + public function buildForSmsAnswer(): string + { + return $this->environmentSettings['smsAnswer']; } /** - * Build the url to the public key - * - * @return string + * Build the URL to the public key */ - public function buildForPublicKey() { - $route = $this->environmentSettings['publicKey']; - return $route; + public function buildForPublicKey(): string + { + return $this->environmentSettings['publicKey']; } /** - * Creates an URL from the request and data provided. + * Creates a URL from the request and data provided. + * + * @param array $request * - * @param array $request * @return string */ - protected function createUrlFromRequestAndLanguage($request) { + protected function createUrlFromRequestAndLanguage(array $request): string + { $url = $this->getPaymentUrl() . '?' . http_build_query($request, '', '&'); - return preg_replace('/[\r\n]+/is', '', $url); + + return preg_replace('/[\r\n]+/is', '', $url) ?? ''; } /** - * Returns payment url. Argument is same as lang parameter in request data + * Returns payment URL. Argument is same as lang parameter in request data * - * @return string $url + * @return string */ - public function getPaymentUrl() { - $route = $this->environmentSettings['payment']; - return $route; + public function getPaymentUrl(): string + { + return $this->environmentSettings['payment']; } } diff --git a/src/WebToPay/Util.php b/src/WebToPay/Util.php index a263819..66831c6 100644 --- a/src/WebToPay/Util.php +++ b/src/WebToPay/Util.php @@ -11,37 +11,27 @@ class WebToPay_Util /** * Decodes url-safe-base64 encoded string * Url-safe-base64 is same as base64, but + is replaced to - and / to _ - * - * @param string $encodedText - * - * @return string */ - public function decodeSafeUrlBase64($encodedText) { - return base64_decode(strtr($encodedText, array('-' => '+', '_' => '/'))); + public function decodeSafeUrlBase64(string $encodedText): string + { + return base64_decode(strtr($encodedText, '-_', '+/')); } /** * Encodes string to url-safe-base64 * Url-safe-base64 is same as base64, but + is replaced to - and / to _ - * - * @param string $text - * - * @return string */ - public function encodeSafeUrlBase64($text) { - return strtr(base64_encode($text), array('+' => '-', '/' => '_')); + public function encodeSafeUrlBase64(string $text): string + { + return strtr(base64_encode($text), '+/', '-_'); } /** * Decrypts string with aes-256-gcm algorithm - * - * @param string $stringToDecrypt - * @param string $key - * - * @return string|null */ - function decryptGCM($stringToDecrypt, $key) { - $ivLength = openssl_cipher_iv_length(self::GCM_CIPHER); + public function decryptGCM(string $stringToDecrypt, string $key): ?string + { + $ivLength = (int) openssl_cipher_iv_length(self::GCM_CIPHER); $iv = substr($stringToDecrypt, 0, $ivLength); $ciphertext = substr($stringToDecrypt, $ivLength, -self::GCM_AUTH_KEY_LENGTH); $tag = substr($stringToDecrypt, -self::GCM_AUTH_KEY_LENGTH); @@ -63,11 +53,13 @@ function decryptGCM($stringToDecrypt, $key) { * * @param string $query * - * @return array + * @return array */ - public function parseHttpQuery($query) { - $params = array(); + public function parseHttpQuery(string $query): array + { + $params = []; parse_str($query, $params); + return $params; } @@ -76,11 +68,12 @@ public function parseHttpQuery($query) { * * @param mixed $data * - * @return mixed + * @return array|string */ - protected function stripSlashesRecursively($data) { + protected function stripSlashesRecursively($data) + { if (is_array($data)) { - $result = array(); + $result = []; foreach ($data as $key => $value) { $result[stripslashes($key)] = $this->stripSlashesRecursively($value); } diff --git a/src/WebToPay/WebClient.php b/src/WebToPay/WebClient.php index 1f944a9..6608f44 100644 --- a/src/WebToPay/WebClient.php +++ b/src/WebToPay/WebClient.php @@ -3,56 +3,54 @@ /** * Simple web client */ -class WebToPay_WebClient { - +class WebToPay_WebClient +{ /** * Gets page contents by specified URI. Adds query data if provided to the URI * Ignores status code of the response and header fields * * @param string $uri - * @param array $queryData + * @param array $queryData * * @return string * * @throws WebToPayException */ - public function get($uri, array $queryData = array()) { - if (count($queryData) > 0) { - $uri .= strpos($uri, '?') === false ? '?' : '&'; - $uri .= http_build_query($queryData, '', '&'); + public function get(string $uri, array $queryData = []): string + { + // Append query data to the URI if provided + if (!empty($queryData)) { + $uri .= (strpos($uri, '?') === false ? '?' : '&') + . http_build_query($queryData, '', '&'); } + + // Parse URL $url = parse_url($uri); - if ('https' == $url['scheme']) { - $host = 'ssl://'.$url['host']; - $port = 443; - } else { - $host = $url['host']; - $port = 80; - } + $scheme = isset($url['scheme']) ? $url['scheme'] : 'http'; + $host = $url['host'] ?? ''; + $port = $scheme === 'https' ? 443 : 80; + $path = $url['path'] ?? '/'; + $query = isset($url['query']) ? '?' . $url['query'] : ''; + // Open socket connection $fp = fsockopen($host, $port, $errno, $errstr, 30); if (!$fp) { throw new WebToPayException(sprintf('Cannot connect to %s', $uri), WebToPayException::E_INVALID); } - if(isset($url['query'])) { - $data = $url['path'].'?'.$url['query']; - } else { - $data = $url['path']; - } - - $out = "GET " . $data . " HTTP/1.0\r\n"; - $out .= "Host: ".$url['host']."\r\n"; + // Construct HTTP request + $out = "GET {$path}{$query} HTTP/1.1\r\n"; + $out .= "Host: {$host}\r\n"; $out .= "Connection: Close\r\n\r\n"; - $content = ''; - + // Send request and read response fwrite($fp, $out); - while (!feof($fp)) $content .= fgets($fp, 8192); + $content = (string) stream_get_contents($fp); fclose($fp); + // Separate header and content list($header, $content) = explode("\r\n\r\n", $content, 2); return trim($content); } -} \ No newline at end of file +} diff --git a/src/WebToPayException.php b/src/WebToPayException.php index c599bf2..c98f015 100644 --- a/src/WebToPayException.php +++ b/src/WebToPayException.php @@ -3,83 +3,83 @@ /** * Base exception class for all exceptions in this library */ -class WebToPayException extends Exception { - +class WebToPayException extends Exception +{ /** * Missing field. */ - const E_MISSING = 1; + public const E_MISSING = 1; /** * Invalid field value. */ - const E_INVALID = 2; + public const E_INVALID = 2; /** * Max length exceeded. */ - const E_MAXLEN = 3; + public const E_MAXLEN = 3; /** * Regexp for field value doesn't match. */ - const E_REGEXP = 4; + public const E_REGEXP = 4; /** * Missing or invalid user given parameters. */ - const E_USER_PARAMS = 5; + public const E_USER_PARAMS = 5; /** * Logging errors */ - const E_LOG = 6; + public const E_LOG = 6; /** * SMS answer errors */ - const E_SMS_ANSWER = 7; + public const E_SMS_ANSWER = 7; /** * Macro answer errors */ - const E_STATUS = 8; + public const E_STATUS = 8; /** * Library errors - if this happens, bug-report should be sent; also you can check for newer version */ - const E_LIBRARY = 9; + public const E_LIBRARY = 9; /** * Errors in remote service - it returns some invalid data */ - const E_SERVICE = 10; - + public const E_SERVICE = 10; + /** * Deprecated usage errors */ - const E_DEPRECATED_USAGE = 11; + public const E_DEPRECATED_USAGE = 11; /** - * @var string|boolean + * @var string|bool */ protected $fieldName = false; /** * Sets field which failed - * - * @param string $fieldName */ - public function setField($fieldName) { + public function setField(string $fieldName): void + { $this->fieldName = $fieldName; } /** * Gets field which failed * - * @return string|boolean false + * @return string|bool */ - public function getField() { + public function getField() + { return $this->fieldName; } -} \ No newline at end of file +} diff --git a/src/includes.php b/src/includes.php index b8dd3b0..c514199 100644 --- a/src/includes.php +++ b/src/includes.php @@ -21,4 +21,4 @@ include(dirname(__FILE__) . '/WebToPay/Util.php'); include(dirname(__FILE__) . '/WebToPay/WebClient.php'); include(dirname(__FILE__) . '/WebToPay/UrlBuilder.php'); -} \ No newline at end of file +} From 64da41055d74a17515aeb8ad4e62623fd50cd252 Mon Sep 17 00:00:00 2001 From: Oleksandr Gribiennikov Date: Tue, 16 Apr 2024 14:43:59 +0300 Subject: [PATCH 02/23] Code Style fixing (using PHP-CS-Fixer) --- .gitignore | 5 +++ .php-cs-fixer.dist.php | 20 +++++++++++ composer.json | 3 +- src/WebToPay.php | 40 ++++++++++++---------- src/WebToPay/CallbackValidator.php | 4 ++- src/WebToPay/Exception/Callback.php | 8 +++-- src/WebToPay/Exception/Configuration.php | 6 ++-- src/WebToPay/Exception/Validation.php | 2 ++ src/WebToPay/Factory.php | 8 +++-- src/WebToPay/PaymentMethod.php | 2 ++ src/WebToPay/PaymentMethodCountry.php | 11 +++--- src/WebToPay/PaymentMethodGroup.php | 15 ++++++-- src/WebToPay/PaymentMethodList.php | 4 ++- src/WebToPay/PaymentMethodListProvider.php | 5 +-- src/WebToPay/RequestBuilder.php | 2 ++ src/WebToPay/Sign/SS1SignChecker.php | 2 ++ src/WebToPay/Sign/SS2SignChecker.php | 2 ++ src/WebToPay/Sign/SignCheckerInterface.php | 2 ++ src/WebToPay/SmsAnswerSender.php | 2 ++ src/WebToPay/UrlBuilder.php | 4 ++- src/WebToPay/Util.php | 8 +++-- src/WebToPay/WebClient.php | 6 ++-- src/WebToPayException.php | 2 ++ src/includes.php | 2 ++ 24 files changed, 120 insertions(+), 45 deletions(-) create mode 100644 .php-cs-fixer.dist.php diff --git a/.gitignore b/.gitignore index 8b7ef35..823a5ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ /vendor composer.lock +.composer +.phpunit.result.cache +coverage +.bash_history +.php-cs-fixer.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..fa6dfde --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,20 @@ +in([ + __DIR__ . DIRECTORY_SEPARATOR . 'src', + __DIR__ . DIRECTORY_SEPARATOR . 'tests', + ]) +; + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + '@PHP82Migration' => true, + 'strict_param' => true, + 'array_syntax' => ['syntax' => 'short'], + 'declare_strict_types' => true, + ]) + ->setFinder($finder) +; diff --git a/composer.json b/composer.json index e6e6ecb..fabd682 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "require": { "php": ">=7.4", "ext-simplexml": "*", - "ext-openssl": "*" + "ext-openssl": "*", + "ext-libxml": "*" }, "require-dev": { "phpunit/phpunit": "^9.6", diff --git a/src/WebToPay.php b/src/WebToPay.php index d46b354..902de10 100644 --- a/src/WebToPay.php +++ b/src/WebToPay.php @@ -1,4 +1,6 @@ $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $url = $factory->getRequestBuilder() ->buildRequestUrlFromData($data); @@ -149,7 +151,7 @@ public static function buildRepeatRequest(array $data): array $projectId = $data['projectid']; $orderId = $data['orderid']; - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $requestBuilder = $factory->getRequestBuilder(); return $requestBuilder->buildRepeatRequest($orderId); @@ -163,9 +165,9 @@ public static function buildRepeatRequest(array $data): array */ public static function getPaymentUrl(string $language = 'LIT'): string { - return (in_array($language, array('lt', 'lit', 'LIT'))) - ? self::PAY_URL - : self::PAYSERA_PAY_URL; + return (in_array($language, ['lt', 'lit', 'LIT'], true)) + ? self::PAY_URL + : self::PAYSERA_PAY_URL; } /** @@ -207,7 +209,7 @@ public static function checkResponse(array $query, array $userData = []) return $data; } catch (WebToPayException $exception) { - if ($logFile && $exception->getCode() != WebToPayException::E_DEPRECATED_USAGE) { + if ($logFile && $exception->getCode() != WebToPayException::E_DEPRECATED_USAGE) { self::log('ERR', $exception . "\nQuery: " . http_build_query($query, '', '&'), $logFile); } throw $exception; @@ -229,7 +231,7 @@ public static function checkResponse(array $query, array $userData = []) */ public static function validateAndParseData(array $query, ?int $projectId, ?string $password): array { - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $validator = $factory->getCallbackValidator(); return $validator->validateAndParseData($query); @@ -252,11 +254,11 @@ public static function smsAnswer(array $userData): void $smsId = $userData['id']; $text = $userData['msg']; $password = $userData['sign_password']; - $logFile = isset($userData['log']) ? $userData['log'] : null; + $logFile = $userData['log'] ?? null; try { - $factory = new WebToPay_Factory(array('password' => $password)); + $factory = new WebToPay_Factory(['password' => $password]); $factory->getSmsAnswerSender()->sendAnswer($smsId, $text); if ($logFile) { @@ -287,7 +289,7 @@ public static function getPaymentMethodList( float $amount, string $currency = 'EUR' ): WebToPay_PaymentMethodList { - $factory = new WebToPay_Factory(array('projectId' => $projectId)); + $factory = new WebToPay_Factory(['projectId' => $projectId]); return $factory->getPaymentMethodListProvider()->getPaymentMethodList($amount, $currency); } @@ -306,13 +308,13 @@ protected static function log(string $type, string $msg, string $logfile): void return; } - $logline = array( + $logline = [ $type, - isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-', + $_SERVER['REMOTE_ADDR'] ?? '-', date('[Y-m-d H:i:s O]'), 'v' . self::VERSION . ':', - $msg - ); + $msg, + ]; $logline = implode(' ', $logline)."\n"; fwrite($fp, $logline); diff --git a/src/WebToPay/CallbackValidator.php b/src/WebToPay/CallbackValidator.php index cfce357..9ced609 100644 --- a/src/WebToPay/CallbackValidator.php +++ b/src/WebToPay/CallbackValidator.php @@ -1,5 +1,7 @@ @@ -229,7 +231,7 @@ protected function getWebClient(): WebToPay_WebClient * * @throws WebToPay_Exception_Configuration */ - protected function getUtil():WebToPay_Util + protected function getUtil(): WebToPay_Util { if ($this->util === null) { $this->util = new WebToPay_Util(); diff --git a/src/WebToPay/PaymentMethod.php b/src/WebToPay/PaymentMethod.php index 1f55408..49a3672 100644 --- a/src/WebToPay/PaymentMethod.php +++ b/src/WebToPay/PaymentMethod.php @@ -1,5 +1,7 @@ $titleTranslations * @param string $defaultLanguage */ - public function __construct(string $countryCode, array $titleTranslations, string $defaultLanguage = 'lt') { + public function __construct(string $countryCode, array $titleTranslations, string $defaultLanguage = 'lt') + { $this->countryCode = $countryCode; $this->defaultLanguage = $defaultLanguage; $this->titleTranslations = $titleTranslations; @@ -120,7 +123,7 @@ public function getGroups(): array */ public function getPaymentMethods(): array { - $paymentMethods = array(); + $paymentMethods = []; foreach ($this->groups as $group) { $paymentMethods = array_merge($paymentMethods, $group->getPaymentMethods()); } @@ -180,7 +183,7 @@ public function fromXmlNode(SimpleXMLElement $countryNode): void { foreach ($countryNode->payment_group as $groupNode) { $key = (string) $groupNode->attributes()->key; - $titleTranslations = array(); + $titleTranslations = []; foreach ($groupNode->title as $titleNode) { $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; } @@ -190,7 +193,7 @@ public function fromXmlNode(SimpleXMLElement $countryNode): void /** * Method to create new group instances. Overwrite if you have to use some other group subtype. - * + * * @param string $groupKey * @param array $translations * diff --git a/src/WebToPay/PaymentMethodGroup.php b/src/WebToPay/PaymentMethodGroup.php index 94f0b2e..6197815 100644 --- a/src/WebToPay/PaymentMethodGroup.php +++ b/src/WebToPay/PaymentMethodGroup.php @@ -1,5 +1,7 @@ paymentMethods[$key]) ? $this->paymentMethods[$key] : null; + return $this->paymentMethods[$key] ?? null; } /** @@ -203,7 +205,14 @@ public function fromXmlNode(SimpleXMLElement $groupNode): void $baseCurrency = (string)$paymentTypeNode->base_currency; } $this->addPaymentMethod($this->createPaymentMethod( - $key, $minAmount, $maxAmount, $currency, $logoTranslations, $titleTranslations, $isIban, $baseCurrency + $key, + $minAmount, + $maxAmount, + $currency, + $logoTranslations, + $titleTranslations, + $isIban, + $baseCurrency )); } } @@ -230,7 +239,7 @@ protected function createPaymentMethod( array $logoList = [], array $titleTranslations = [], bool $isIban = false, - $baseCurrency = null + $baseCurrency = null ): WebToPay_PaymentMethod { return new WebToPay_PaymentMethod( $key, diff --git a/src/WebToPay/PaymentMethodList.php b/src/WebToPay/PaymentMethodList.php index e817fe8..c62977d 100644 --- a/src/WebToPay/PaymentMethodList.php +++ b/src/WebToPay/PaymentMethodList.php @@ -1,5 +1,7 @@ countries[$countryCode]) ? $this->countries[$countryCode] : null; + return $this->countries[$countryCode] ?? null; } /** diff --git a/src/WebToPay/PaymentMethodListProvider.php b/src/WebToPay/PaymentMethodListProvider.php index f32fa8e..bc70c2f 100644 --- a/src/WebToPay/PaymentMethodListProvider.php +++ b/src/WebToPay/PaymentMethodListProvider.php @@ -1,5 +1,7 @@ projectId = $projectId; $this->webClient = $webClient; $this->urlBuilder = $urlBuilder; diff --git a/src/WebToPay/RequestBuilder.php b/src/WebToPay/RequestBuilder.php index 11e86b0..aafee7b 100644 --- a/src/WebToPay/RequestBuilder.php +++ b/src/WebToPay/RequestBuilder.php @@ -1,5 +1,7 @@ diff --git a/src/WebToPay/Util.php b/src/WebToPay/Util.php index 66831c6..0bd1c2d 100644 --- a/src/WebToPay/Util.php +++ b/src/WebToPay/Util.php @@ -1,12 +1,14 @@ Date: Tue, 16 Apr 2024 16:04:20 +0300 Subject: [PATCH 03/23] Tests fixing --- src/WebToPay/CallbackValidator.php | 3 ++- src/WebToPay/Factory.php | 4 ++-- src/WebToPay/RequestBuilder.php | 4 ++-- tests/WebToPay/CallbackValidatorTest.php | 5 ++++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/WebToPay/CallbackValidator.php b/src/WebToPay/CallbackValidator.php index 9ced609..e0c6ff8 100644 --- a/src/WebToPay/CallbackValidator.php +++ b/src/WebToPay/CallbackValidator.php @@ -114,7 +114,8 @@ public function checkExpectedFields(array $data, array $expected): void { foreach ($expected as $key => $value) { $passedValue = $data[$key] ?? null; - if ($passedValue !== $value) { + // there should be non-strict comparison here + if ($passedValue != $value) { throw new WebToPayException( sprintf('Field %s is not as expected (expected %s, got %s)', $key, $value, $passedValue) ); diff --git a/src/WebToPay/Factory.php b/src/WebToPay/Factory.php index 9c06d10..4c4aba1 100644 --- a/src/WebToPay/Factory.php +++ b/src/WebToPay/Factory.php @@ -119,7 +119,7 @@ public function getRequestBuilder(): WebToPay_RequestBuilder throw new WebToPay_Exception_Configuration('You have to provide project ID'); } $this->requestBuilder = new WebToPay_RequestBuilder( - $this->configuration['projectId'], + (int) $this->configuration['projectId'], $this->configuration['password'], $this->getUtil(), $this->getUrlBuilder() @@ -174,7 +174,7 @@ public function getPaymentMethodListProvider(): WebToPay_PaymentMethodListProvid throw new WebToPay_Exception_Configuration('You have to provide project ID'); } $this->paymentMethodListProvider = new WebToPay_PaymentMethodListProvider( - $this->configuration['projectId'], + (int) $this->configuration['projectId'], $this->getWebClient(), $this->getUrlBuilder() ); diff --git a/src/WebToPay/RequestBuilder.php b/src/WebToPay/RequestBuilder.php index aafee7b..5e7f7a0 100644 --- a/src/WebToPay/RequestBuilder.php +++ b/src/WebToPay/RequestBuilder.php @@ -123,7 +123,7 @@ protected function validateRequest(array $data, array $specs): void } if (!empty($data[$name])) { - if ($maxlen && strlen($data[$name]) > $maxlen) { + if ($maxlen && strlen((string) $data[$name]) > $maxlen) { throw new WebToPay_Exception_Validation(sprintf( "'%s' value is too long (%d), %d characters allowed.", $name, @@ -132,7 +132,7 @@ protected function validateRequest(array $data, array $specs): void ), WebToPayException::E_MAXLEN, $name); } - if ($regexp !== '' && !preg_match($regexp, $data[$name])) { + if ($regexp !== '' && !preg_match($regexp, (string) $data[$name])) { throw new WebToPay_Exception_Validation( sprintf("'%s' value '%s' is invalid.", $name, $data[$name]), WebToPayException::E_REGEXP, diff --git a/tests/WebToPay/CallbackValidatorTest.php b/tests/WebToPay/CallbackValidatorTest.php index d2e1721..a2e5992 100644 --- a/tests/WebToPay/CallbackValidatorTest.php +++ b/tests/WebToPay/CallbackValidatorTest.php @@ -107,7 +107,10 @@ public function testValidateAndParseDataWithDecryptionFailure() { $request = array('data' => $encryptedDataString); $this->util->expects($this->once())->method('decodeSafeUrlBase64')->with($urlSafeEncodedString)->will($this->returnValue($encryptedDataString)); - $this->util->expects($this->once())->method('decryptGCM')->with($encryptedDataString, self::PROJECT_PASSWORD)->will($this->returnValue(false)); + $this->util->expects($this->once()) + ->method('decryptGCM') + ->with($encryptedDataString, self::PROJECT_PASSWORD) + ->willReturn(null); $this->validator->validateAndParseData($request); } From 8636bab0db1ad1407aa42719251498027f3406e4 Mon Sep 17 00:00:00 2001 From: Oleksandr Gribiennikov Date: Tue, 16 Apr 2024 16:05:34 +0300 Subject: [PATCH 04/23] Update build configuration and built `WebToPay.php` file --- WebToPay.php | 2689 +++++++++++++++++++++++------------------------ build/build.xml | 18 +- 2 files changed, 1303 insertions(+), 1404 deletions(-) diff --git a/WebToPay.php b/WebToPay.php index c51ae5e..bfc44ab 100644 --- a/WebToPay.php +++ b/WebToPay.php @@ -2,6 +2,8 @@ /* * This file is autogenerated, DO NOT EDIT */ + +declare(strict_types=1); /** * PHP Library for WebToPay provided services. * Copyright (C) 2012 http://www.webtopay.com/ @@ -26,36 +28,35 @@ * @link http://www.webtopay.com/ */ - /** * Contains static methods for most used scenarios. */ -class WebToPay { - +class WebToPay +{ /** * WebToPay Library version. */ - const VERSION = '1.6'; + public const VERSION = '1.6'; /** * Server URL where all requests should go. */ - const PAY_URL = 'https://bank.paysera.com/pay/'; + public const PAY_URL = 'https://bank.paysera.com/pay/'; /** * Server URL where all non-lithuanian language requests should go. */ - const PAYSERA_PAY_URL = 'https://bank.paysera.com/pay/'; + public const PAYSERA_PAY_URL = 'https://bank.paysera.com/pay/'; /** * Server URL where we can get XML with payment method data. */ - const XML_URL = 'https://www.paysera.com/new/api/paymentMethods/'; + public const XML_URL = 'https://www.paysera.com/new/api/paymentMethods/'; /** * SMS answer url. */ - const SMS_ANSWER_URL = 'https://bank.paysera.com/psms/respond/'; + public const SMS_ANSWER_URL = 'https://bank.paysera.com/psms/respond/'; /** * Builds request data array. @@ -66,13 +67,14 @@ class WebToPay { * Possible keys: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request + * @param array $data Information about current payment request * - * @return array + * @return array * * @throws WebToPayException on data validation error */ - public static function buildRequest($data) { + public static function buildRequest(array $data): array + { if (!isset($data['sign_password']) || !isset($data['projectid'])) { throw new WebToPayException('sign_password or projectid is not provided'); } @@ -81,24 +83,25 @@ public static function buildRequest($data) { unset($data['sign_password']); unset($data['projectid']); - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRequest($data); } - /** * Builds request and redirects user to payment window with generated request data * * Possible array keys are described here: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request. + * @param array $data Information about current payment request. * @param boolean $exit if true, exits after sending Location header; default false * * @throws WebToPayException on data validation error */ - public static function redirectToPayment($data, $exit = false) { + public static function redirectToPayment(array $data, bool $exit = false): void + { if (!isset($data['sign_password']) || !isset($data['projectid'])) { throw new WebToPayException('sign_password or projectid is not provided'); } @@ -107,7 +110,7 @@ public static function redirectToPayment($data, $exit = false) { unset($data['sign_password']); unset($data['projectid']); - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $url = $factory->getRequestBuilder() ->buildRequestUrlFromData($data); @@ -136,13 +139,14 @@ public static function redirectToPayment($data, $exit = false) { * keys are described here: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request + * @param array $data Information about current payment request * - * @return array + * @return array * * @throws WebToPayException on data validation error */ - public static function buildRepeatRequest($data) { + public static function buildRepeatRequest(array $data): array + { if (!isset($data['sign_password']) || !isset($data['projectid']) || !isset($data['orderid'])) { throw new WebToPayException('sign_password, projectid or orderid is not provided'); } @@ -150,21 +154,23 @@ public static function buildRepeatRequest($data) { $projectId = $data['projectid']; $orderId = $data['orderid']; - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRepeatRequest($orderId); } /** * Returns payment url. Argument is same as lang parameter in request data * - * @param string $language + * @param string $language * @return string $url */ - public static function getPaymentUrl($language = 'LIT') { - return (in_array($language, array('lt', 'lit', 'LIT'))) - ? self::PAY_URL - : self::PAYSERA_PAY_URL; + public static function getPaymentUrl(string $language = 'LIT'): string + { + return (in_array($language, ['lt', 'lit', 'LIT'], true)) + ? self::PAY_URL + : self::PAYSERA_PAY_URL; } /** @@ -180,18 +186,19 @@ public static function getPaymentUrl($language = 'LIT') { * * If response is not correct, WebToPayException will be raised. * - * @param array $query Response array - * @param array $userData + * @param array $query Response array + * @param array $userData * - * @return array + * @return array * * @throws WebToPayException * @deprecated use validateAndParseData() and check status code yourself */ - public static function checkResponse($query, $userData = array()) { - $projectId = isset($userData['projectid']) ? $userData['projectid'] : null; - $password = isset($userData['sign_password']) ? $userData['sign_password'] : null; - $logFile = isset($userData['log']) ? $userData['log'] : null; + public static function checkResponse(array $query, array $userData = []) + { + $projectId = isset($userData['projectid']) ? (int) $userData['projectid'] : null; + $password = isset($userData['sign_password']) ? (string) $userData['sign_password'] : null; + $logFile = isset($userData['log']) ? (string) $userData['log'] : null; try { $data = self::validateAndParseData($query, $projectId, $password); @@ -205,7 +212,7 @@ public static function checkResponse($query, $userData = array()) { return $data; } catch (WebToPayException $exception) { - if ($logFile && $exception->getCode() != WebToPayException::E_DEPRECATED_USAGE) { + if ($logFile && $exception->getCode() != WebToPayException::E_DEPRECATED_USAGE) { self::log('ERR', $exception . "\nQuery: " . http_build_query($query, '', '&'), $logFile); } throw $exception; @@ -215,30 +222,34 @@ public static function checkResponse($query, $userData = array()) { /** * Parses request (query) data and validates its signature. * - * @param array $query usually $_GET - * @param integer $projectId - * @param string $password + * @param array $query usually $_GET + * @param int|null $projectId + * @param string|null $password * - * @return array + * @return array * * @throws WebToPayException + * @throws WebToPay_Exception_Callback + * @throws WebToPay_Exception_Configuration */ - public static function validateAndParseData(array $query, $projectId, $password) { - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + public static function validateAndParseData(array $query, ?int $projectId, ?string $password): array + { + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $validator = $factory->getCallbackValidator(); - $data = $validator->validateAndParseData($query); - return $data; + + return $validator->validateAndParseData($query); } /** * Sends SMS answer * - * @param array $userData + * @param array $userData * * @throws WebToPayException * @throws WebToPay_Exception_Validation */ - public static function smsAnswer($userData) { + public static function smsAnswer(array $userData): void + { if (!isset($userData['id']) || !isset($userData['msg']) || !isset($userData['sign_password'])) { throw new WebToPay_Exception_Validation('id, msg and sign_password are required'); } @@ -246,11 +257,11 @@ public static function smsAnswer($userData) { $smsId = $userData['id']; $text = $userData['msg']; $password = $userData['sign_password']; - $logFile = isset($userData['log']) ? $userData['log'] : null; + $logFile = $userData['log'] ?? null; try { - $factory = new WebToPay_Factory(array('password' => $password)); + $factory = new WebToPay_Factory(['password' => $password]); $factory->getSmsAnswerSender()->sendAnswer($smsId, $text); if ($logFile) { @@ -263,10 +274,8 @@ public static function smsAnswer($userData) { } throw $e; } - } - /** * Gets available payment methods for project. Gets methods min and max amounts in specified currency. * @@ -278,8 +287,13 @@ public static function smsAnswer($userData) { * * @throws WebToPayException */ - public static function getPaymentMethodList($projectId, $amount, $currency = 'EUR') { - $factory = new WebToPay_Factory(array('projectId' => $projectId)); + public static function getPaymentMethodList( + int $projectId, + float $amount, + string $currency = 'EUR' + ): WebToPay_PaymentMethodList { + $factory = new WebToPay_Factory(['projectId' => $projectId]); + return $factory->getPaymentMethodListProvider()->getPaymentMethodList($amount, $currency); } @@ -290,19 +304,20 @@ public static function getPaymentMethodList($projectId, $amount, $currency = 'EU * @param string $msg * @param string $logfile */ - protected static function log($type, $msg, $logfile) { + protected static function log(string $type, string $msg, string $logfile): void + { $fp = @fopen($logfile, 'a'); if (!$fp) { return; } - $logline = array( + $logline = [ $type, - isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-', + $_SERVER['REMOTE_ADDR'] ?? '-', date('[Y-m-d H:i:s O]'), 'v' . self::VERSION . ':', - $msg - ); + $msg, + ]; $logline = implode(' ', $logline)."\n"; fwrite($fp, $logline); @@ -317,150 +332,153 @@ protected static function log($type, $msg, $logfile) { } - - /** * Base exception class for all exceptions in this library */ -class WebToPayException extends Exception { - +class WebToPayException extends Exception +{ /** * Missing field. */ - const E_MISSING = 1; + public const E_MISSING = 1; /** * Invalid field value. */ - const E_INVALID = 2; + public const E_INVALID = 2; /** * Max length exceeded. */ - const E_MAXLEN = 3; + public const E_MAXLEN = 3; /** * Regexp for field value doesn't match. */ - const E_REGEXP = 4; + public const E_REGEXP = 4; /** * Missing or invalid user given parameters. */ - const E_USER_PARAMS = 5; + public const E_USER_PARAMS = 5; /** * Logging errors */ - const E_LOG = 6; + public const E_LOG = 6; /** * SMS answer errors */ - const E_SMS_ANSWER = 7; + public const E_SMS_ANSWER = 7; /** * Macro answer errors */ - const E_STATUS = 8; + public const E_STATUS = 8; /** * Library errors - if this happens, bug-report should be sent; also you can check for newer version */ - const E_LIBRARY = 9; + public const E_LIBRARY = 9; /** * Errors in remote service - it returns some invalid data */ - const E_SERVICE = 10; - + public const E_SERVICE = 10; + /** * Deprecated usage errors */ - const E_DEPRECATED_USAGE = 11; + public const E_DEPRECATED_USAGE = 11; /** - * @var string|boolean + * @var string|bool */ protected $fieldName = false; /** * Sets field which failed - * - * @param string $fieldName */ - public function setField($fieldName) { + public function setField(string $fieldName): void + { $this->fieldName = $fieldName; } /** * Gets field which failed * - * @return string|boolean false + * @return string|bool */ - public function getField() { + public function getField() + { return $this->fieldName; } } + /** * Class to hold information about payment method */ -class WebToPay_PaymentMethod { +class WebToPay_PaymentMethod +{ /** * Assigned key for this payment method - * - * @var string */ - protected $key; + protected string $key; + + protected ?int $minAmount; + + protected ?int $maxAmount; + + protected ?string $currency; /** * Logo url list by language. Usually logo is same for all languages, but exceptions exist * - * @var array + * @var array */ - protected $logoList; + protected array $logoList; /** * Title list by language * - * @var array + * @var array */ - protected $titleTranslations; + protected array $titleTranslations; /** * Default language to use for titles - * - * @var string */ - protected $defaultLanguage; + protected string $defaultLanguage; - /** - * @var boolean - */ - protected $isIban; + protected bool $isIban; - /** - * @var string - */ - protected $baseCurrency; + protected ?string $baseCurrency; /** * Constructs object * - * @param string $key - * @param integer $minAmount - * @param integer $maxAmount - * @param string $currency - * @param array $logoList - * @param array $titleTranslations - * @param string $defaultLanguage - * @param bool $isIban - * @param string $baseCurrency + * @param string $key + * @param integer|null $minAmount + * @param integer|null $maxAmount + * @param string|null $currency + * @param array $logoList + * @param array $titleTranslations + * @param string $defaultLanguage + * @param bool $isIban + * @param string|null $baseCurrency */ public function __construct( - $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), - $defaultLanguage = 'lt', $isIban = false, $baseCurrency = null + string $key, + ?int $minAmount, + ?int $maxAmount, + ?string $currency, + array $logoList = [], + array $titleTranslations = [], + string $defaultLanguage = 'lt', + bool $isIban = false, + ?string $baseCurrency = null ) { $this->key = $key; $this->minAmount = $minAmount; @@ -476,43 +494,36 @@ public function __construct( /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethod */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethod + { $this->defaultLanguage = $language; + return $this; } /** * Gets default language for titles - * - * @return string */ - public function getDefaultLanguage() { + public function getDefaultLanguage(): string + { return $this->defaultLanguage; } /** * Get assigned payment method key - * - * @return string */ - public function getKey() { + public function getKey(): string + { return $this->key; } /** * Gets logo url for this payment method. Uses specified language or default one. * If logotype is not found for specified language, null is returned. - * - * @param string [Optional] $languageCode - * - * @return string|null */ - public function getLogoUrl($languageCode = null) { + public function getLogoUrl(?string $languageCode = null): ?string + { if ($languageCode !== null && isset($this->logoList[$languageCode])) { return $this->logoList[$languageCode]; } elseif (isset($this->logoList[$this->defaultLanguage])) { @@ -524,12 +535,9 @@ public function getLogoUrl($languageCode = null) { /** * Gets title for this payment method. Uses specified language or default one. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { + public function getTitle(?string $languageCode = null): string + { if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { return $this->titleTranslations[$languageCode]; } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { @@ -543,20 +551,17 @@ public function getTitle($languageCode = null) { * Checks if this payment method can be used for specified amount. * Throws exception if currency checked is not the one, for which payment method list was downloaded. * - * @param integer $amount - * @param string $currency - * - * @return boolean - * * @throws WebToPayException */ - public function isAvailableForAmount($amount, $currency) { + public function isAvailableForAmount(int $amount, string $currency): bool + { if ($this->currency !== $currency) { throw new WebToPayException( 'Currencies does not match. You have to get payment types for the currency you are checking. Given currency: ' . $currency . ', available currency: ' . $this->currency ); } + return ( ($this->minAmount === null || $amount >= $this->minAmount) && ($this->maxAmount === null || $amount <= $this->maxAmount) @@ -565,56 +570,48 @@ public function isAvailableForAmount($amount, $currency) { /** * Returns min amount for this payment method. If no min amount is specified, returns empty string. - * - * @return string */ - public function getMinAmountAsString() { + public function getMinAmountAsString(): string + { return $this->minAmount === null ? '' : ($this->minAmount . ' ' . $this->currency); } /** * Returns max amount for this payment method. If no max amount is specified, returns empty string. - * - * @return string */ - public function getMaxAmountAsString() { + public function getMaxAmountAsString(): string + { return $this->maxAmount === null ? '' : ($this->maxAmount . ' ' . $this->currency); } /** * Set if this method returns IBAN number after payment - * - * @param boolean $isIban */ - public function setIsIban($isIban) { + public function setIsIban(bool $isIban): void + { $this->isIban = $isIban == 1; } /** * Get if this method returns IBAN number after payment - * - * @return bool */ - public function isIban() { + public function isIban(): bool + { return $this->isIban; } /** * Setter of BaseCurrency - * - * @param string $baseCurrency */ - public function setBaseCurrency($baseCurrency) + public function setBaseCurrency(string $baseCurrency): void { $this->baseCurrency = $baseCurrency; } /** * Getter of BaseCurrency - * - * @return string */ - public function getBaseCurrency() + public function getBaseCurrency(): ?string { return $this->baseCurrency; } @@ -622,1270 +619,904 @@ public function getBaseCurrency() /** - * Creates objects. Also caches to avoid creating several instances of same objects + * Utility class */ -class WebToPay_Factory { - - const ENV_PRODUCTION = 'production'; - const ENV_SANDBOX = 'sandbox'; +class WebToPay_Util +{ + public const GCM_CIPHER = 'aes-256-gcm'; + public const GCM_AUTH_KEY_LENGTH = 16; /** - * @var array + * Decodes url-safe-base64 encoded string + * Url-safe-base64 is same as base64, but + is replaced to - and / to _ */ - protected static $defaultConfiguration = array( - 'routes' => array( - self::ENV_PRODUCTION => array( - 'publicKey' => 'https://www.paysera.com/download/public.key', - 'payment' => 'https://bank.paysera.com/pay/', - 'paymentMethodList' => 'https://www.paysera.com/new/api/paymentMethods/', - 'smsAnswer' => 'https://bank.paysera.com/psms/respond/', - ), - self::ENV_SANDBOX => array( - 'publicKey' => 'https://sandbox.paysera.com/download/public.key', - 'payment' => 'https://sandbox.paysera.com/pay/', - 'paymentMethodList' => 'https://sandbox.paysera.com/new/api/paymentMethods/', - 'smsAnswer' => 'https://sandbox.paysera.com/psms/respond/', - ), - ) - ); + public function decodeSafeUrlBase64(string $encodedText): string + { + return base64_decode(strtr($encodedText, '-_', '+/'), true); + } /** - * @var string + * Encodes string to url-safe-base64 + * Url-safe-base64 is same as base64, but + is replaced to - and / to _ */ - protected $environment; + public function encodeSafeUrlBase64(string $text): string + { + return strtr(base64_encode($text), '+/', '-_'); + } /** - * @var array + * Decrypts string with aes-256-gcm algorithm */ - protected $configuration; + public function decryptGCM(string $stringToDecrypt, string $key): ?string + { + $ivLength = (int) openssl_cipher_iv_length(self::GCM_CIPHER); + $iv = substr($stringToDecrypt, 0, $ivLength); + $ciphertext = substr($stringToDecrypt, $ivLength, -self::GCM_AUTH_KEY_LENGTH); + $tag = substr($stringToDecrypt, -self::GCM_AUTH_KEY_LENGTH); - /** - * @var WebToPay_WebClient - */ - protected $webClient = null; + $decryptedText = openssl_decrypt( + $ciphertext, + self::GCM_CIPHER, + $key, + OPENSSL_RAW_DATA, + $iv, + $tag + ); - /** - * @var WebToPay_CallbackValidator - */ - protected $callbackValidator = null; + return $decryptedText === false ? null : $decryptedText; + } /** - * @var WebToPay_RequestBuilder + * Parses HTTP query to array + * + * @param string $query + * + * @return array */ - protected $requestBuilder = null; + public function parseHttpQuery(string $query): array + { + $params = []; + parse_str($query, $params); - /** - * @var WebToPay_Sign_SignCheckerInterface - */ - protected $signer = null; + return $params; + } /** - * @var WebToPay_SmsAnswerSender + * Strips slashes recursively, so this method can be used on arrays with more than one level + * + * @param mixed $data + * + * @return array|string */ - protected $smsAnswerSender = null; + protected function stripSlashesRecursively($data) + { + if (is_array($data)) { + $result = []; + foreach ($data as $key => $value) { + $result[stripslashes($key)] = $this->stripSlashesRecursively($value); + } + return $result; + } else { + return stripslashes($data); + } + } +} + + +/** + * Raised on validation error in passed data when building the request + */ +class WebToPay_Exception_Validation extends WebToPayException +{ + public function __construct( + string $message, + int $code = 0, + ?string $field = null, + ?Exception $previousException = null + ) { + parent::__construct($message, $code, $previousException); + if ($field) { + $this->setField($field); + } + } +} + + +/** + * Raised on error in callback + */ +class WebToPay_Exception_Callback extends WebToPayException +{ +} + + +/** + * Raised if configuration is incorrect + */ +class WebToPay_Exception_Configuration extends WebToPayException +{ +} + + +/** + * Payment method configuration for some country + */ +class WebToPay_PaymentMethodCountry +{ + protected string $countryCode; /** - * @var WebToPay_PaymentMethodListProvider + * Holds available payment types for this country + * + * @var WebToPay_PaymentMethodGroup[] */ - protected $paymentMethodListProvider = null; + protected array $groups; /** - * @var WebToPay_Util + * Default language for titles */ - protected $util = null; + protected string $defaultLanguage; /** - * @var WebToPay_UrlBuilder + * Translations array for this country. Holds associative array of country title by language codes. + * + * @var array */ - protected $urlBuilder = null; - + protected array $titleTranslations; /** - * Constructs object. - * Configuration keys: projectId, password - * They are required only when some object being created needs them, - * if they are not found at that moment - exception is thrown + * Constructs object * - * @param array $configuration + * @param string $countryCode + * @param array $titleTranslations + * @param string $defaultLanguage */ - public function __construct(array $configuration = array()) { - - $this->configuration = array_merge(self::$defaultConfiguration, $configuration); - $this->environment = self::ENV_PRODUCTION; + public function __construct(string $countryCode, array $titleTranslations, string $defaultLanguage = 'lt') + { + $this->countryCode = $countryCode; + $this->defaultLanguage = $defaultLanguage; + $this->titleTranslations = $titleTranslations; + $this->groups = []; } /** - * If passed true the factory will use sandbox when constructing URLs - * - * @param $enableSandbox - * @return self + * Sets default language for titles. + * Returns itself for fluent interface */ - public function useSandbox($enableSandbox) + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodCountry { - if ($enableSandbox) { - $this->environment = self::ENV_SANDBOX; - } else { - $this->environment = self::ENV_PRODUCTION; + $this->defaultLanguage = $language; + foreach ($this->groups as $group) { + $group->setDefaultLanguage($language); } + return $this; } /** - * Creates or gets callback validator instance - * - * @return WebToPay_CallbackValidator - * - * @throws WebToPay_Exception_Configuration + * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not + * specified, uses default language, given to constructor. */ - public function getCallbackValidator() { - if ($this->callbackValidator === null) { - if (!isset($this->configuration['projectId'])) { - throw new WebToPay_Exception_Configuration('You have to provide project ID'); - } - - $this->callbackValidator = new WebToPay_CallbackValidator( - $this->configuration['projectId'], - $this->getSigner(), - $this->getUtil(), - isset($this->configuration['password']) ? $this->configuration['password'] : null - ); + public function getTitle(?string $languageCode = null): string + { + if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { + return $this->titleTranslations[$languageCode]; + } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { + return $this->titleTranslations[$this->defaultLanguage]; + } else { + return $this->countryCode; } + } - return $this->callbackValidator; + /** + * Gets default language for titles + */ + public function getDefaultLanguage(): string + { + return $this->defaultLanguage; } /** - * Creates or gets request builder instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_RequestBuilder + * Gets country code */ - public function getRequestBuilder() { - if ($this->requestBuilder === null) { - if (!isset($this->configuration['password'])) { - throw new WebToPay_Exception_Configuration('You have to provide project password to sign request'); - } - if (!isset($this->configuration['projectId'])) { - throw new WebToPay_Exception_Configuration('You have to provide project ID'); - } - $this->requestBuilder = new WebToPay_RequestBuilder( - $this->configuration['projectId'], - $this->configuration['password'], - $this->getUtil(), - $this->getUrlBuilder() - ); - } - return $this->requestBuilder; - } - - /** - * @return WebToPay_UrlBuilder - */ - public function getUrlBuilder() { - if ($this->urlBuilder === null) { - $this->urlBuilder = new WebToPay_UrlBuilder( - $this->configuration, - $this->environment - ); - } - return $this->urlBuilder; - } - - /** - * Creates or gets SMS answer sender instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_SmsAnswerSender - */ - public function getSmsAnswerSender() { - if ($this->smsAnswerSender === null) { - if (!isset($this->configuration['password'])) { - throw new WebToPay_Exception_Configuration('You have to provide project password'); - } - $this->smsAnswerSender = new WebToPay_SmsAnswerSender( - $this->configuration['password'], - $this->getWebClient(), - $this->getUrlBuilder() - ); - } - return $this->smsAnswerSender; - } - - /** - * Creates or gets payment list provider instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_PaymentMethodListProvider - */ - public function getPaymentMethodListProvider() { - if ($this->paymentMethodListProvider === null) { - if (!isset($this->configuration['projectId'])) { - throw new WebToPay_Exception_Configuration('You have to provide project ID'); - } - $this->paymentMethodListProvider = new WebToPay_PaymentMethodListProvider( - $this->configuration['projectId'], - $this->getWebClient(), - $this->getUrlBuilder() - ); - } - return $this->paymentMethodListProvider; - } - - /** - * Creates or gets signer instance. Chooses SS2 signer if openssl functions are available, SS1 in other case - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_Sign_SignCheckerInterface - * - * @throws WebToPayException - */ - protected function getSigner() { - if ($this->signer === null) { - if (function_exists('openssl_pkey_get_public')) { - $webClient = $this->getWebClient(); - $publicKey = $webClient->get($this->getUrlBuilder()->buildForPublicKey()); - if (!$publicKey) { - throw new WebToPayException('Cannot download public key from WebToPay website'); - } - $this->signer = new WebToPay_Sign_SS2SignChecker($publicKey, $this->getUtil()); - } else { - if (!isset($this->configuration['password'])) { - throw new WebToPay_Exception_Configuration( - 'You have to provide project password if OpenSSL is unavailable' - ); - } - $this->signer = new WebToPay_Sign_SS1SignChecker($this->configuration['password']); - } - } - return $this->signer; - } - - /** - * Creates or gets web client instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_WebClient - */ - protected function getWebClient() { - if ($this->webClient === null) { - $this->webClient = new WebToPay_WebClient(); - } - return $this->webClient; - } - - /** - * Creates or gets util instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_Util - */ - protected function getUtil() { - if ($this->util === null) { - $this->util = new WebToPay_Util(); - } - return $this->util; - } -} - - -/** - * Wrapper class to group payment methods. Each country can have several payment method groups, each of them - * have one or more payment methods. - */ -class WebToPay_PaymentMethodGroup { - /** - * Some unique (in the scope of country) key for this group - * - * @var string - */ - protected $groupKey; - - /** - * Translations array for this group. Holds associative array of group title by country codes. - * - * @var array - */ - protected $translations; - - /** - * Holds actual payment methods - * - * @var WebToPay_PaymentMethod[] - */ - protected $paymentMethods; - - /** - * Default language for titles - * - * @var string - */ - protected $defaultLanguage; - - /** - * Constructs object - * - * @param string $groupKey - * @param array $translations - * @param string $defaultLanguage - */ - public function __construct($groupKey, array $translations = array(), $defaultLanguage = 'lt') { - $this->groupKey = $groupKey; - $this->translations = $translations; - $this->defaultLanguage = $defaultLanguage; - $this->paymentMethods = array(); - } - - /** - * Sets default language for titles. - * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodGroup - */ - public function setDefaultLanguage($language) { - $this->defaultLanguage = $language; - foreach ($this->paymentMethods as $paymentMethod) { - $paymentMethod->setDefaultLanguage($language); - } - return $this; + public function getCode(): string + { + return $this->countryCode; } /** - * Gets default language for titles - * - * @return string + * Adds new group to payment methods for this country. + * If some other group was registered earlier with same key, overwrites it. + * Returns given group */ - public function getDefaultLanguage() { - return $this->defaultLanguage; + public function addGroup(WebToPay_PaymentMethodGroup $group): WebToPay_PaymentMethodGroup + { + return $this->groups[$group->getKey()] = $group; } /** - * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not - * specified, uses default language, given to constructor. - * - * @param string [Optional] $languageCode - * - * @return string + * Gets group object with specified group key. If no group with such key is found, returns null. */ - public function getTitle($languageCode = null) { - if ($languageCode !== null && isset($this->translations[$languageCode])) { - return $this->translations[$languageCode]; - } elseif (isset($this->translations[$this->defaultLanguage])) { - return $this->translations[$this->defaultLanguage]; - } else { - return $this->groupKey; - } + public function getGroup(string $groupKey): ?WebToPay_PaymentMethodGroup + { + return $this->groups[$groupKey] ?? null; } /** - * Returns group key + * Returns payment method groups registered for this country. * - * @return string + * @return WebToPay_PaymentMethodGroup[] */ - public function getKey() { - return $this->groupKey; + public function getGroups(): array + { + return $this->groups; } /** - * Returns available payment methods for this group + * Gets payment methods in all groups * * @return WebToPay_PaymentMethod[] */ - public function getPaymentMethods() { - return $this->paymentMethods; - } - - - /** - * Adds new payment method for this group. - * If some other payment method with specified key was registered earlier, overwrites it. - * Returns given payment method - * - * @param WebToPay_PaymentMethod $paymentMethod - * - * @return WebToPay_PaymentMethod - */ - public function addPaymentMethod(WebToPay_PaymentMethod $paymentMethod) { - return $this->paymentMethods[$paymentMethod->getKey()] = $paymentMethod; - } - - /** - * Gets payment method object with key. If no payment method with such key is found, returns null. - * - * @param string $key - * - * @return null|WebToPay_PaymentMethod - */ - public function getPaymentMethod($key) { - return isset($this->paymentMethods[$key]) ? $this->paymentMethods[$key] : null; - } - - /** - * Returns new group instance with only those payment methods, which are available for provided amount. - * - * @param integer $amount - * @param string $currency - * - * @return WebToPay_PaymentMethodGroup - */ - public function filterForAmount($amount, $currency) { - $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); - foreach ($this->getPaymentMethods() as $paymentMethod) { - if ($paymentMethod->isAvailableForAmount($amount, $currency)) { - $group->addPaymentMethod($paymentMethod); - } - } - return $group; - } - - /** - * Returns new country instance with only those payment methods, which are returns or not iban number after payment - * - * @param boolean $isIban - * - * @return WebToPay_PaymentMethodGroup - */ - public function filterForIban($isIban = true) { - $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); - foreach ($this->getPaymentMethods() as $paymentMethod) { - if ($paymentMethod->isIban() == $isIban) { - $group->addPaymentMethod($paymentMethod); - } + public function getPaymentMethods(): array + { + $paymentMethods = []; + foreach ($this->groups as $group) { + $paymentMethods = array_merge($paymentMethods, $group->getPaymentMethods()); } - return $group; - } - /** - * Returns whether this group has no payment methods - * - * @return boolean - */ - public function isEmpty() { - return count($this->paymentMethods) === 0; + return $paymentMethods; } /** - * Loads payment methods from given XML node - * - * @param SimpleXMLElement $groupNode + * Returns new country instance with only those payment methods, which are available for provided amount. */ - public function fromXmlNode($groupNode) { - foreach ($groupNode->payment_type as $paymentTypeNode) { - $key = (string) $paymentTypeNode->attributes()->key; - $titleTranslations = array(); - foreach ($paymentTypeNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; - } - $logoTranslations = array(); - foreach ($paymentTypeNode->logo_url as $logoNode) { - if ((string) $logoNode !== '') { - $logoTranslations[(string) $logoNode->attributes()->language] = (string) $logoNode; - } - } - $minAmount = null; - $maxAmount = null; - $currency = null; - $isIban = false; - $baseCurrency = null; - if (isset($paymentTypeNode->min)) { - $minAmount = (int) $paymentTypeNode->min->attributes()->amount; - $currency = (string) $paymentTypeNode->min->attributes()->currency; - } - if (isset($paymentTypeNode->max)) { - $maxAmount = (int) $paymentTypeNode->max->attributes()->amount; - $currency = (string) $paymentTypeNode->max->attributes()->currency; - } - - if (isset($paymentTypeNode->is_iban)) { - $isIban = (int) $paymentTypeNode->is_iban; - } - if (isset($paymentTypeNode->base_currency)) { - $baseCurrency = (string) $paymentTypeNode->base_currency; + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodCountry + { + $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); + foreach ($this->getGroups() as $group) { + $group = $group->filterForAmount($amount, $currency); + if (!$group->isEmpty()) { + $country->addGroup($group); } - $this->addPaymentMethod($this->createPaymentMethod( - $key, $minAmount, $maxAmount, $currency, $logoTranslations, $titleTranslations, $isIban, $baseCurrency - )); } - } - - /** - * Method to create new payment method instances. Overwrite if you have to use some other subclass. - * - * @param string $key - * @param integer $minAmount - * @param integer $maxAmount - * @param string $currency - * @param array $logoList - * @param array $titleTranslations - * @param bool $isIban - * @param null $baseCurrency - * - * @return WebToPay_PaymentMethod - */ - protected function createPaymentMethod( - $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), - $isIban = false, $baseCurrency = null - ) { - return new WebToPay_PaymentMethod( - $key, $minAmount, $maxAmount, $currency, $logoList, $titleTranslations, $this->defaultLanguage, - $isIban, $baseCurrency - ); - } -} - -/** - * Parses and validates callbacks - */ -class WebToPay_CallbackValidator { - /** - * @var WebToPay_Sign_SignCheckerInterface - */ - protected $signer; - - /** - * @var WebToPay_Util - */ - protected $util; - - /** - * @var integer - */ - protected $projectId; - - /** - * @var string|null - */ - protected $password; - - /** - * Constructs object - * - * @param integer $projectId - * @param WebToPay_Sign_SignCheckerInterface $signer - * @param WebToPay_Util $util - * @param string|null $password - */ - public function __construct( - $projectId, - WebToPay_Sign_SignCheckerInterface $signer, - WebToPay_Util $util, - $password = null - ) { - $this->signer = $signer; - $this->util = $util; - $this->projectId = $projectId; - $this->password = $password; + return $country; } /** - * Parses callback parameters from query parameters and checks if sign is correct. - * Request has parameter "data", which is signed and holds all callback parameters - * - * @param array $requestData - * - * @return array Parsed callback parameters - * - * @throws WebToPayException - * @throws WebToPay_Exception_Callback + * Returns new country instance with only those payment methods, which are returns or not iban number after payment */ - public function validateAndParseData(array $requestData) { - if (!isset($requestData['data'])) { - throw new WebToPay_Exception_Callback('"data" parameter not found'); - } - - $data = $requestData['data']; - - if (isset($requestData['ss1']) || isset($requestData['ss2'])) { - if (!$this->signer->checkSign($requestData)) { - throw new WebToPay_Exception_Callback('Invalid sign parameters, check $_GET length limit'); - } - - $queryString = $this->util->decodeSafeUrlBase64($data); - } else { - if (null === $this->password) { - throw new WebToPay_Exception_Configuration('You have to provide project password'); - } - - $queryString = $this->util->decryptGCM( - $this->util->decodeSafeUrlBase64($data), - $this->password - ); + public function filterForIban(bool $isIban = true): WebToPay_PaymentMethodCountry + { + $country = new WebToPay_PaymentMethodCountry( + $this->countryCode, + $this->titleTranslations, + $this->defaultLanguage + ); - if (null === $queryString) { - throw new WebToPay_Exception_Callback('Callback data decryption failed'); + foreach ($this->getGroups() as $group) { + $group = $group->filterForIban($isIban); + if (!$group->isEmpty()) { + $country->addGroup($group); } } - $request = $this->util->parseHttpQuery($queryString); - if (!isset($request['projectid'])) { - throw new WebToPay_Exception_Callback( - 'Project ID not provided in callback', - WebToPayException::E_INVALID - ); - } - - if ((string) $request['projectid'] !== (string) $this->projectId) { - throw new WebToPay_Exception_Callback( - sprintf('Bad projectid: %s, should be: %s', $request['projectid'], $this->projectId), - WebToPayException::E_INVALID - ); - } - - if (!isset($request['type']) || !in_array($request['type'], array('micro', 'macro'))) { - $micro = ( - isset($request['to']) - && isset($request['from']) - && isset($request['sms']) - ); - $request['type'] = $micro ? 'micro' : 'macro'; - } - - return $request; - } - - /** - * Checks data to have all the same parameters provided in expected array - * - * @param array $data - * @param array $expected - * - * @throws WebToPayException - */ - public function checkExpectedFields(array $data, array $expected) { - foreach ($expected as $key => $value) { - $passedValue = isset($data[$key]) ? $data[$key] : null; - if ($passedValue != $value) { - throw new WebToPayException( - sprintf('Field %s is not as expected (expected %s, got %s)', $key, $value, $passedValue) - ); - } - } + return $country; } -} - -/** - * Class with all information about available payment methods for some project, optionally filtered by some amount. - */ -class WebToPay_PaymentMethodList { /** - * Holds available payment countries - * - * @var WebToPay_PaymentMethodCountry[] - */ - protected $countries; - - /** - * Default language for titles - * - * @var string - */ - protected $defaultLanguage; - - /** - * Project ID, to which this method list is valid - * - * @var integer - */ - protected $projectId; - - /** - * Currency for min and max amounts in this list - * - * @var string - */ - protected $currency; - - /** - * If this list is filtered for some amount, this field defines it - * - * @var integer - */ - protected $amount; - - /** - * Constructs object - * - * @param integer $projectId - * @param string $currency currency for min and max amounts in this list - * @param string $defaultLanguage - * @param integer $amount null if this list is not filtered by amount + * Returns whether this country has no groups */ - public function __construct($projectId, $currency, $defaultLanguage = 'lt', $amount = null) { - $this->projectId = $projectId; - $this->countries = array(); - $this->defaultLanguage = $defaultLanguage; - $this->currency = $currency; - $this->amount = $amount; + public function isEmpty(): bool + { + return count($this->groups) === 0; } /** - * Sets default language for titles. - * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodList + * Loads groups from given XML node */ - public function setDefaultLanguage($language) { - $this->defaultLanguage = $language; - foreach ($this->countries as $country) { - $country->setDefaultLanguage($language); + public function fromXmlNode(SimpleXMLElement $countryNode): void + { + foreach ($countryNode->payment_group as $groupNode) { + $key = (string) $groupNode->attributes()->key; + $titleTranslations = []; + foreach ($groupNode->title as $titleNode) { + $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + } + $this->addGroup($this->createGroup($key, $titleTranslations))->fromXmlNode($groupNode); } - return $this; } /** - * Gets default language for titles + * Method to create new group instances. Overwrite if you have to use some other group subtype. * - * @return string - */ - public function getDefaultLanguage() { - return $this->defaultLanguage; - } - - /** - * Gets project ID for this payment method list + * @param string $groupKey + * @param array $translations * - * @return integer + * @return WebToPay_PaymentMethodGroup */ - public function getProjectId() { - return $this->projectId; + protected function createGroup(string $groupKey, array $translations = []): WebToPay_PaymentMethodGroup + { + return new WebToPay_PaymentMethodGroup($groupKey, $translations, $this->defaultLanguage); } +} + + +/** + * Builds and signs requests + */ +class WebToPay_RequestBuilder +{ + protected string $projectPassword; + + protected WebToPay_Util $util; + + protected int $projectId; + + protected WebToPay_UrlBuilder $urlBuilder; /** - * Gets currency for min and max amounts in this list - * - * @return string + * Constructs object */ - public function getCurrency() { - return $this->currency; + public function __construct( + int $projectId, + string $projectPassword, + WebToPay_Util $util, + WebToPay_UrlBuilder $urlBuilder + ) { + $this->projectId = $projectId; + $this->projectPassword = $projectPassword; + $this->util = $util; + $this->urlBuilder = $urlBuilder; } /** - * Gets whether this list is already filtered for some amount + * Builds request data array. * - * @return boolean + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. + * + * @param array $data information about current payment request + * + * @return array + * + * @throws WebToPayException */ - public function isFiltered() { - return $this->amount !== null; + public function buildRequest(array $data): array + { + $this->validateRequest($data, self::getRequestSpec()); + $data['version'] = WebToPay::VERSION; + $data['projectid'] = $this->projectId; + unset($data['repeat_request']); + + return $this->createRequest($data); } /** - * Returns available countries + * Builds the full request url (including the protocol and the domain) * - * @return WebToPay_PaymentMethodCountry[] + * @param array $data + * @return string + * @throws WebToPayException */ - public function getCountries() { - return $this->countries; + public function buildRequestUrlFromData(array $data): string + { + $language = $data['lang'] ?? null; + $request = $this->buildRequest($data); + + return $this->urlBuilder->buildForRequest($request, $language); } /** - * Adds new country to payment methods. If some other country with same code was registered earlier, overwrites it. - * Returns added country instance + * Builds repeat request data array. + * + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. + * + * @param string $orderId order id of repeated request * - * @param WebToPay_PaymentMethodCountry $country + * @return array * - * @return WebToPay_PaymentMethodCountry + * @throws WebToPayException */ - public function addCountry(WebToPay_PaymentMethodCountry $country) { - return $this->countries[$country->getCode()] = $country; + public function buildRepeatRequest(string $orderId): array + { + $data['orderid'] = $orderId; + $data['version'] = WebToPay::VERSION; + $data['projectid'] = $this->projectId; + $data['repeat_request'] = '1'; + + return $this->createRequest($data); } /** - * Gets country object with specified country code. If no country with such country code is found, returns null. - * - * @param string $countryCode + * Builds the full request url for a repeated request (including the protocol and the domain) * - * @return null|WebToPay_PaymentMethodCountry + * @throws WebToPayException */ - public function getCountry($countryCode) { - return isset($this->countries[$countryCode]) ? $this->countries[$countryCode] : null; + public function buildRepeatRequestUrlFromOrderId(string $orderId): string + { + $request = $this->buildRepeatRequest($orderId); + + return $this->urlBuilder->buildForRequest($request); } /** - * Returns new payment method list instance with only those payment methods, which are available for provided - * amount. - * Returns itself, if list is already filtered and filter amount matches the given one. - * - * @param integer $amount - * @param string $currency + * Checks data to be valid by passed specification * - * @return WebToPay_PaymentMethodList + * @param array $data + * @param array $specs * - * @throws WebToPayException if this list is already filtered and not for provided amount + * @throws WebToPay_Exception_Validation */ - public function filterForAmount($amount, $currency) { - if ($currency !== $this->currency) { - throw new WebToPayException( - 'Currencies do not match. Given currency: ' . $currency . ', currency in list: ' . $this->currency - ); - } - if ($this->isFiltered()) { - if ($this->amount === $amount) { - return $this; - } else { - throw new WebToPayException('This list is already filtered, use unfiltered list instead'); + protected function validateRequest(array $data, array $specs): void + { + foreach ($specs as $spec) { + [$name, $maxlen, $required, $regexp] = $spec; + + if ($required && !isset($data[$name])) { + throw new WebToPay_Exception_Validation( + sprintf("'%s' is required but missing.", $name), + WebToPayException::E_MISSING, + $name + ); } - } else { - $list = new WebToPay_PaymentMethodList($this->projectId, $currency, $this->defaultLanguage, $amount); - foreach ($this->getCountries() as $country) { - $country = $country->filterForAmount($amount, $currency); - if (!$country->isEmpty()) { - $list->addCountry($country); + + if (!empty($data[$name])) { + if ($maxlen && strlen((string) $data[$name]) > $maxlen) { + throw new WebToPay_Exception_Validation(sprintf( + "'%s' value is too long (%d), %d characters allowed.", + $name, + strlen($data[$name]), + $maxlen + ), WebToPayException::E_MAXLEN, $name); + } + + if ($regexp !== '' && !preg_match($regexp, (string) $data[$name])) { + throw new WebToPay_Exception_Validation( + sprintf("'%s' value '%s' is invalid.", $name, $data[$name]), + WebToPayException::E_REGEXP, + $name + ); } } - return $list; } } /** - * Loads countries from given XML node + * Makes request data array from parameters, also generates signature + * + * @param array $request * - * @param SimpleXMLElement $xmlNode + * @return array */ - public function fromXmlNode($xmlNode) { - foreach ($xmlNode->country as $countryNode) { - $titleTranslations = array(); - foreach ($countryNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; - } - $this->addCountry($this->createCountry((string) $countryNode->attributes()->code, $titleTranslations)) - ->fromXmlNode($countryNode); - } + protected function createRequest(array $request): array + { + $data = $this->util->encodeSafeUrlBase64(http_build_query($request, '', '&')); + + return [ + 'data' => $data, + 'sign' => md5($data . $this->projectPassword), + ]; } /** - * Method to create new country instances. Overwrite if you have to use some other country subtype. + * Returns specification of fields for request. * - * @param string $countryCode - * @param array $titleTranslations + * Array structure: + * name – request item name + * maxlen – max allowed value for item + * required – is this item is required + * regexp – regexp to test item value * - * @return WebToPay_PaymentMethodCountry + * @return array> */ - protected function createCountry($countryCode, array $titleTranslations = array()) { - return new WebToPay_PaymentMethodCountry($countryCode, $titleTranslations, $this->defaultLanguage); + protected static function getRequestSpec(): array + { + return [ + ['orderid', 40, true, ''], + ['accepturl', 255, true, ''], + ['cancelurl', 255, true, ''], + ['callbackurl', 255, true, ''], + ['lang', 3, false, '/^[a-z]{3}$/i'], + ['amount', 11, false, '/^\d+$/'], + ['currency', 3, false, '/^[a-z]{3}$/i'], + ['payment', 20, false, ''], + ['country', 2, false, '/^[a-z_]{2}$/i'], + ['paytext', 255, false, ''], + ['p_firstname', 255, false, ''], + ['p_lastname', 255, false, ''], + ['p_email', 255, false, ''], + ['p_street', 255, false, ''], + ['p_city', 255, false, ''], + ['p_state', 255, false, ''], + ['p_zip', 20, false, ''], + ['p_countrycode', 2, false, '/^[a-z]{2}$/i'], + ['test', 1, false, '/^[01]$/'], + ['time_limit', 19, false, '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/'], + ]; } } + /** * Simple web client */ -class WebToPay_WebClient { - +class WebToPay_WebClient +{ /** * Gets page contents by specified URI. Adds query data if provided to the URI * Ignores status code of the response and header fields * * @param string $uri - * @param array $queryData + * @param array $queryData * * @return string * * @throws WebToPayException */ - public function get($uri, array $queryData = array()) { - if (count($queryData) > 0) { - $uri .= strpos($uri, '?') === false ? '?' : '&'; - $uri .= http_build_query($queryData, '', '&'); + public function get(string $uri, array $queryData = []): string + { + // Append query data to the URI if provided + if (!empty($queryData)) { + $uri .= (strpos($uri, '?') === false ? '?' : '&') + . http_build_query($queryData, '', '&'); } + + // Parse URL $url = parse_url($uri); - if ('https' == $url['scheme']) { - $host = 'ssl://'.$url['host']; - $port = 443; - } else { - $host = $url['host']; - $port = 80; - } + $scheme = $url['scheme'] ?? 'http'; + $host = $url['host'] ?? ''; + $port = $scheme === 'https' ? 443 : 80; + $path = $url['path'] ?? '/'; + $query = isset($url['query']) ? '?' . $url['query'] : ''; + // Open socket connection $fp = fsockopen($host, $port, $errno, $errstr, 30); if (!$fp) { throw new WebToPayException(sprintf('Cannot connect to %s', $uri), WebToPayException::E_INVALID); } - if(isset($url['query'])) { - $data = $url['path'].'?'.$url['query']; - } else { - $data = $url['path']; - } - - $out = "GET " . $data . " HTTP/1.0\r\n"; - $out .= "Host: ".$url['host']."\r\n"; + // Construct HTTP request + $out = "GET {$path}{$query} HTTP/1.1\r\n"; + $out .= "Host: {$host}\r\n"; $out .= "Connection: Close\r\n\r\n"; - $content = ''; - + // Send request and read response fwrite($fp, $out); - while (!feof($fp)) $content .= fgets($fp, 8192); + $content = (string) stream_get_contents($fp); fclose($fp); - list($header, $content) = explode("\r\n\r\n", $content, 2); + // Separate header and content + [$header, $content] = explode("\r\n\r\n", $content, 2); return trim($content); } } + /** - * Builds and signs requests + * Sends answer to SMS payment if it was not provided with response to callback */ -class WebToPay_RequestBuilder { +class WebToPay_SmsAnswerSender +{ + protected string $password; + + protected WebToPay_WebClient $webClient; + + protected WebToPay_UrlBuilder $urlBuilder; + + /** + * Constructs object + */ + public function __construct(string $password, WebToPay_WebClient $webClient, WebToPay_UrlBuilder $urlBuilder) + { + $this->password = $password; + $this->webClient = $webClient; + $this->urlBuilder = $urlBuilder; + } + + /** + * Sends answer by sms ID get from callback. Answer can be sent only if it was not provided + * when responding to the callback + * + * @throws WebToPayException + */ + public function sendAnswer(int $smsId, string $text): void + { + $content = $this->webClient->get($this->urlBuilder->buildForSmsAnswer(), [ + 'id' => $smsId, + 'msg' => $text, + 'transaction' => md5($this->password . '|' . $smsId), + ]); + + if (strpos($content, 'OK') !== 0) { + throw new WebToPayException( + sprintf('Error: %s', $content), + WebToPayException::E_SMS_ANSWER + ); + } + } +} + +/** + * Class with all information about available payment methods for some project, optionally filtered by some amount. + */ +class WebToPay_PaymentMethodList +{ /** - * @var string + * Holds available payment countries + * + * @var WebToPay_PaymentMethodCountry[] */ - protected $projectPassword; + protected array $countries; /** - * @var WebToPay_Util + * Default language for titles */ - protected $util; + protected string $defaultLanguage; /** - * @var integer + * Project ID, to which this method list is valid */ - protected $projectId; + protected int $projectId; + /** + * Currency for min and max amounts in this list + */ + protected string $currency; /** - * @var WebToPay_UrlBuilder $urlBuilder + * If this list is filtered for some amount, this field defines it */ - protected $urlBuilder; + protected ?int $amount; /** * Constructs object * - * @param integer $projectId - * @param string $projectPassword - * @param WebToPay_Util $util - * @param WebToPay_UrlBuilder $urlBuilder + * @param int $projectId + * @param string $currency currency for min and max amounts in this list + * @param string $defaultLanguage + * @param int|null $amount null if this list is not filtered by amount */ - public function __construct( - $projectId, - $projectPassword, - WebToPay_Util $util, - WebToPay_UrlBuilder $urlBuilder - ) + public function __construct(int $projectId, string $currency, string $defaultLanguage = 'lt', ?int $amount = null) { $this->projectId = $projectId; - $this->projectPassword = $projectPassword; - $this->util = $util; - $this->urlBuilder = $urlBuilder; + $this->countries = []; + $this->defaultLanguage = $defaultLanguage; + $this->currency = $currency; + $this->amount = $amount; + } + + /** + * Sets default language for titles. + * Returns itself for fluent interface + */ + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodList + { + $this->defaultLanguage = $language; + foreach ($this->countries as $country) { + $country->setDefaultLanguage($language); + } + + return $this; + } + + /** + * Gets default language for titles + */ + public function getDefaultLanguage(): string + { + return $this->defaultLanguage; + } + + /** + * Gets project ID for this payment method list + */ + public function getProjectId(): int + { + return $this->projectId; + } + + /** + * Gets currency for min and max amounts in this list + */ + public function getCurrency(): string + { + return $this->currency; } /** - * Builds request data array. - * - * This method checks all given data and generates correct request data - * array or raises WebToPayException on failure. - * - * @param array $data information about current payment request - * - * @return array - * - * @throws WebToPayException + * Gets whether this list is already filtered for some amount */ - public function buildRequest($data) { - $this->validateRequest($data, self::getRequestSpec()); - $data['version'] = WebToPay::VERSION; - $data['projectid'] = $this->projectId; - unset($data['repeat_request']); - return $this->createRequest($data); + public function isFiltered(): bool + { + return $this->amount !== null; } /** - * Builds the full request url (including the protocol and the domain) + * Returns available countries * - * @param array $data - * @return string + * @return WebToPay_PaymentMethodCountry[] */ - public function buildRequestUrlFromData($data) { - $language = isset($data['lang']) ? $data['lang'] : null; - $request = $this->buildRequest($data); - return $this->urlBuilder->buildForRequest($request, $language); + public function getCountries(): array + { + return $this->countries; } /** - * Builds repeat request data array. - * - * This method checks all given data and generates correct request data - * array or raises WebToPayException on failure. - * - * @param string $orderId order id of repeated request - * - * @return array - * - * @throws WebToPayException + * Adds new country to payment methods. If some other country with same code was registered earlier, overwrites it. + * Returns added country instance */ - public function buildRepeatRequest($orderId) { - $data['orderid'] = $orderId; - $data['version'] = WebToPay::VERSION; - $data['projectid'] = $this->projectId; - $data['repeat_request'] = '1'; - return $this->createRequest($data); + public function addCountry(WebToPay_PaymentMethodCountry $country): WebToPay_PaymentMethodCountry + { + return $this->countries[$country->getCode()] = $country; } /** - * Builds the full request url for a repeated request (including the protocol and the domain) - * - * @param string $orderId order id of repeated request - * @return string + * Gets country object with specified country code. If no country with such country code is found, returns null. */ - public function buildRepeatRequestUrlFromOrderId($orderId) { - $request = $this->buildRepeatRequest($orderId); - return $this->urlBuilder->buildForRequest($request); + public function getCountry(string $countryCode): ?WebToPay_PaymentMethodCountry + { + return $this->countries[$countryCode] ?? null; } /** - * Checks data to be valid by passed specification - * - * @param array $data - * @param array $specs + * Returns new payment method list instance with only those payment methods, which are available for provided + * amount. + * Returns itself, if list is already filtered and filter amount matches the given one. * - * @throws WebToPay_Exception_Validation + * @throws WebToPayException if this list is already filtered and not for provided amount */ - protected function validateRequest($data, $specs) { - foreach ($specs as $spec) { - list($name, $maxlen, $required, $regexp) = $spec; - if ($required && !isset($data[$name])) { - throw new WebToPay_Exception_Validation( - sprintf("'%s' is required but missing.", $name), - WebToPayException::E_MISSING, - $name - ); + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodList + { + if ($currency !== $this->currency) { + throw new WebToPayException( + 'Currencies do not match. Given currency: ' + . $currency + . ', currency in list: ' + . $this->currency + ); + } + if ($this->isFiltered()) { + if ($this->amount === $amount) { + return $this; + } else { + throw new WebToPayException('This list is already filtered, use unfiltered list instead'); } - - if (!empty($data[$name])) { - if ($maxlen && strlen($data[$name]) > $maxlen) { - throw new WebToPay_Exception_Validation(sprintf( - "'%s' value is too long (%d), %d characters allowed.", - $name, - strlen($data[$name]), - $maxlen - ), WebToPayException::E_MAXLEN, $name); - } - - if ($regexp !== '' && !preg_match($regexp, $data[$name])) { - throw new WebToPay_Exception_Validation( - sprintf("'%s' value '%s' is invalid.", $name, $data[$name]), - WebToPayException::E_REGEXP, - $name - ); + } else { + $list = new WebToPay_PaymentMethodList($this->projectId, $currency, $this->defaultLanguage, $amount); + foreach ($this->getCountries() as $country) { + $country = $country->filterForAmount($amount, $currency); + if (!$country->isEmpty()) { + $list->addCountry($country); } } + + return $list; } } /** - * Makes request data array from parameters, also generates signature - * - * @param array $request - * - * @return array + * Loads countries from given XML node */ - protected function createRequest(array $request) { - $data = $this->util->encodeSafeUrlBase64(http_build_query($request, '', '&')); - return array( - 'data' => $data, - 'sign' => md5($data . $this->projectPassword), - ); + public function fromXmlNode(SimpleXMLElement $xmlNode): void + { + foreach ($xmlNode->country as $countryNode) { + $titleTranslations = []; + foreach ($countryNode->title as $titleNode) { + $titleTranslations[(string)$titleNode->attributes()->language] = (string)$titleNode; + } + $this->addCountry($this->createCountry((string)$countryNode->attributes()->code, $titleTranslations)) + ->fromXmlNode($countryNode); + } } /** - * Returns specification of fields for request. + * Method to create new country instances. Overwrite if you have to use some other country subtype. * - * Array structure: - * name – request item name - * maxlen – max allowed value for item - * required – is this item is required - * regexp – regexp to test item value + * @param string $countryCode + * @param array $titleTranslations * - * @return array - */ - protected static function getRequestSpec() { - return array( - array('orderid', 40, true, ''), - array('accepturl', 255, true, ''), - array('cancelurl', 255, true, ''), - array('callbackurl', 255, true, ''), - array('lang', 3, false, '/^[a-z]{3}$/i'), - array('amount', 11, false, '/^\d+$/'), - array('currency', 3, false, '/^[a-z]{3}$/i'), - array('payment', 20, false, ''), - array('country', 2, false, '/^[a-z_]{2}$/i'), - array('paytext', 255, false, ''), - array('p_firstname', 255, false, ''), - array('p_lastname', 255, false, ''), - array('p_email', 255, false, ''), - array('p_street', 255, false, ''), - array('p_city', 255, false, ''), - array('p_state', 255, false, ''), - array('p_zip', 20, false, ''), - array('p_countrycode', 2, false, '/^[a-z]{2}$/i'), - array('test', 1, false, '/^[01]$/'), - array('time_limit', 19, false, '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/'), - ); + * @return WebToPay_PaymentMethodCountry + */ + protected function createCountry(string $countryCode, array $titleTranslations = []): WebToPay_PaymentMethodCountry + { + return new WebToPay_PaymentMethodCountry($countryCode, $titleTranslations, $this->defaultLanguage); } } + /** - * Utility class + * Sign checker which checks SS1 signature. SS1 does not depend on SSL functions */ -class WebToPay_Util +class WebToPay_Sign_SS1SignChecker implements WebToPay_Sign_SignCheckerInterface { - const GCM_CIPHER = 'aes-256-gcm'; - const GCM_AUTH_KEY_LENGTH = 16; + protected string $projectPassword; /** - * Decodes url-safe-base64 encoded string - * Url-safe-base64 is same as base64, but + is replaced to - and / to _ - * - * @param string $encodedText - * - * @return string + * Constructs object */ - public function decodeSafeUrlBase64($encodedText) { - return base64_decode(strtr($encodedText, array('-' => '+', '_' => '/'))); + public function __construct(string $projectPassword) + { + $this->projectPassword = $projectPassword; } /** - * Encodes string to url-safe-base64 - * Url-safe-base64 is same as base64, but + is replaced to - and / to _ + * Check for SS1, which is not depend on openssl functions. * - * @param string $text + * @param array $request * - * @return string + * @return bool + * + * @throws WebToPay_Exception_Callback */ - public function encodeSafeUrlBase64($text) { - return strtr(base64_encode($text), array('+' => '-', '/' => '_')); + public function checkSign(array $request): bool + { + if (!isset($request['data']) || !isset($request['ss1'])) { + throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + } + + return md5($request['data'] . $this->projectPassword) === $request['ss1']; } +} - /** - * Decrypts string with aes-256-gcm algorithm - * - * @param string $stringToDecrypt - * @param string $key - * - * @return string|null - */ - function decryptGCM($stringToDecrypt, $key) { - $ivLength = openssl_cipher_iv_length(self::GCM_CIPHER); - $iv = substr($stringToDecrypt, 0, $ivLength); - $ciphertext = substr($stringToDecrypt, $ivLength, -self::GCM_AUTH_KEY_LENGTH); - $tag = substr($stringToDecrypt, -self::GCM_AUTH_KEY_LENGTH); - $decryptedText = openssl_decrypt( - $ciphertext, - self::GCM_CIPHER, - $key, - OPENSSL_RAW_DATA, - $iv, - $tag - ); +/** + * Checks SS2 signature. Depends on SSL functions + */ +class WebToPay_Sign_SS2SignChecker implements WebToPay_Sign_SignCheckerInterface +{ + protected string $publicKey; - return $decryptedText === false ? null : $decryptedText; - } + protected WebToPay_Util $util; /** - * Parses HTTP query to array - * - * @param string $query - * - * @return array + * Constructs object */ - public function parseHttpQuery($query) { - $params = array(); - parse_str($query, $params); - return $params; + public function __construct(string $publicKey, WebToPay_Util $util) + { + $this->publicKey = $publicKey; + $this->util = $util; } /** - * Strips slashes recursively, so this method can be used on arrays with more than one level + * Checks signature * - * @param mixed $data + * @param array $request + * + * @return bool * - * @return mixed + * @throws WebToPay_Exception_Callback */ - protected function stripSlashesRecursively($data) { - if (is_array($data)) { - $result = array(); - foreach ($data as $key => $value) { - $result[stripslashes($key)] = $this->stripSlashesRecursively($value); - } - return $result; - } else { - return stripslashes($data); + public function checkSign(array $request): bool + { + if (!isset($request['data']) || !isset($request['ss2'])) { + throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); } + + $ss2 = $this->util->decodeSafeUrlBase64($request['ss2']); + $ok = openssl_verify($request['data'], $ss2, $this->publicKey); + + return $ok === 1; } } /** - * Sends answer to SMS payment if it was not provided with response to callback + * Interface for sign checker */ -class WebToPay_SmsAnswerSender { - - /** - * @var string - */ - protected $password; - - /** - * @var WebToPay_WebClient - */ - protected $webClient; - - /** - * @var WebToPay_UrlBuilder $urlBuilder - */ - protected $urlBuilder; - - /** - * Constructs object - * - * @param string $password - * @param WebToPay_WebClient $webClient - * @param WebToPay_UrlBuilder $urlBuilder - */ - public function __construct( - $password, - WebToPay_WebClient $webClient, - WebToPay_UrlBuilder $urlBuilder - ) { - $this->password = $password; - $this->webClient = $webClient; - $this->urlBuilder = $urlBuilder; - } - +interface WebToPay_Sign_SignCheckerInterface +{ /** - * Sends answer by sms ID get from callback. Answer can be send only if it was not provided - * when responding to callback + * Checks whether request is signed properly * - * @param integer $smsId - * @param string $text + * @param array $request * - * @throws WebToPayException + * @return boolean */ - public function sendAnswer($smsId, $text) { - $content = $this->webClient->get($this->urlBuilder->buildForSmsAnswer(), array( - 'id' => $smsId, - 'msg' => $text, - 'transaction' => md5($this->password . '|' . $smsId), - )); - if (strpos($content, 'OK') !== 0) { - throw new WebToPayException( - sprintf('Error: %s', $content), - WebToPayException::E_SMS_ANSWER - ); - } - } + public function checkSign(array $request): bool; } @@ -1893,47 +1524,34 @@ public function sendAnswer($smsId, $text) { * Loads data about payment methods and constructs payment method list object from that data * You need SimpleXML support to use this feature */ -class WebToPay_PaymentMethodListProvider { - - /** - * @var integer - */ - protected $projectId; +class WebToPay_PaymentMethodListProvider +{ + protected int $projectId; - /** - * @var WebToPay_WebClient - */ - protected $webClient; + protected WebToPay_WebClient $webClient; /** * Holds constructed method lists by currency * * @var WebToPay_PaymentMethodList[] */ - protected $methodListCache = array(); + protected array $methodListCache = []; /** * Builds various request URLs - * - * @var WebToPay_UrlBuilder $urlBuilder */ - protected $urlBuilder; + protected WebToPay_UrlBuilder $urlBuilder; /** * Constructs object * - * @param integer $projectId - * @param WebToPay_WebClient $webClient - * @param WebToPay_UrlBuilder $urlBuilder - * * @throws WebToPayException if SimpleXML is not available */ public function __construct( - $projectId, + int $projectId, WebToPay_WebClient $webClient, WebToPay_UrlBuilder $urlBuilder - ) - { + ) { $this->projectId = $projectId; $this->webClient = $webClient; $this->urlBuilder = $urlBuilder; @@ -1946,16 +1564,14 @@ public function __construct( /** * Gets payment method list for specified currency * - * @param float $amount - * @param string $currency - * - * @return WebToPay_PaymentMethodList - * * @throws WebToPayException */ - public function getPaymentMethodList($amount, $currency) { + public function getPaymentMethodList(float $amount, string $currency): WebToPay_PaymentMethodList + { if (!isset($this->methodListCache[$currency])) { - $xmlAsString = $this->webClient->get($this->urlBuilder->buildForPaymentsMethodList($this->projectId, $amount, $currency)); + $xmlAsString = $this->webClient->get( + $this->urlBuilder->buildForPaymentsMethodList($this->projectId, (string) $amount, $currency) + ); $useInternalErrors = libxml_use_internal_errors(false); $rootNode = simplexml_load_string($xmlAsString); libxml_clear_errors(); @@ -1967,134 +1583,249 @@ public function getPaymentMethodList($amount, $currency) { $methodList->fromXmlNode($rootNode); $this->methodListCache[$currency] = $methodList; } + return $this->methodListCache[$currency]; } } + /** - * Raised if configuration is incorrect + * Creates objects. Also caches to avoid creating several instances of same objects */ -class WebToPay_Exception_Configuration extends WebToPayException { +class WebToPay_Factory +{ + public const ENV_PRODUCTION = 'production'; + public const ENV_SANDBOX = 'sandbox'; -} + /** + * @var array + */ + protected static array $defaultConfiguration = [ + 'routes' => [ + self::ENV_PRODUCTION => [ + 'publicKey' => 'https://www.paysera.com/download/public.key', + 'payment' => 'https://bank.paysera.com/pay/', + 'paymentMethodList' => 'https://www.paysera.com/new/api/paymentMethods/', + 'smsAnswer' => 'https://bank.paysera.com/psms/respond/', + ], + self::ENV_SANDBOX => [ + 'publicKey' => 'https://sandbox.paysera.com/download/public.key', + 'payment' => 'https://sandbox.paysera.com/pay/', + 'paymentMethodList' => 'https://sandbox.paysera.com/new/api/paymentMethods/', + 'smsAnswer' => 'https://sandbox.paysera.com/psms/respond/', + ], + ], + ]; + protected string $environment; -/** - * Raised on validation error in passed data when building the request - */ -class WebToPay_Exception_Validation extends WebToPayException { + /** + * @var array + */ + protected array $configuration; - public function __construct($message, $code = 0, $field = null, Exception $previousException = null) { - parent::__construct($message, $code, $previousException); - if ($field) { - $this->setField($field); - } - } -} + protected ?WebToPay_WebClient $webClient = null; -/** - * Raised on error in callback - */ -class WebToPay_Exception_Callback extends WebToPayException { + protected ?WebToPay_CallbackValidator $callbackValidator = null; -} + protected ?WebToPay_RequestBuilder $requestBuilder = null; -/** - * Sign checker which checks SS1 signature. SS1 does not depend on SSL functions - */ -class WebToPay_Sign_SS1SignChecker implements WebToPay_Sign_SignCheckerInterface { + protected ?WebToPay_Sign_SignCheckerInterface $signer = null; + + protected ?WebToPay_SmsAnswerSender $smsAnswerSender = null; + + protected ?WebToPay_PaymentMethodListProvider $paymentMethodListProvider = null; + + protected ?WebToPay_Util $util = null; + + protected ?WebToPay_UrlBuilder $urlBuilder = null; /** - * @var string + * Constructs object. + * Configuration keys: projectId, password + * They are required only when some object being created needs them, + * if they are not found at that moment - exception is thrown + * + * @param array $configuration */ - protected $projectPassword; + public function __construct(array $configuration = []) + { + $this->configuration = array_merge(self::$defaultConfiguration, $configuration); + $this->environment = self::ENV_PRODUCTION; + } /** - * Constructs object - * - * @param string $projectPassword + * If passed true the factory will use sandbox when constructing URLs */ - public function __construct($projectPassword) { - $this->projectPassword = $projectPassword; + public function useSandbox(bool $enableSandbox): self + { + if ($enableSandbox) { + $this->environment = self::ENV_SANDBOX; + } else { + $this->environment = self::ENV_PRODUCTION; + } + + return $this; } /** - * Check for SS1, which is not depend on openssl functions. - * - * @param array $request + * Creates or gets callback validator instance * - * @return boolean + * @throws WebToPay_Exception_Configuration + */ + public function getCallbackValidator(): WebToPay_CallbackValidator + { + if ($this->callbackValidator === null) { + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + + $this->callbackValidator = new WebToPay_CallbackValidator( + (int) $this->configuration['projectId'], + $this->getSigner(), + $this->getUtil(), + $this->configuration['password'] ?? null + ); + } + + return $this->callbackValidator; + } + + /** + * Creates or gets request builder instance * - * @throws WebToPay_Exception_Callback + * @throws WebToPay_Exception_Configuration */ - public function checkSign(array $request) { - if (!isset($request['data']) || !isset($request['ss1'])) { - throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + public function getRequestBuilder(): WebToPay_RequestBuilder + { + if ($this->requestBuilder === null) { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration('You have to provide project password to sign request'); + } + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + $this->requestBuilder = new WebToPay_RequestBuilder( + (int) $this->configuration['projectId'], + $this->configuration['password'], + $this->getUtil(), + $this->getUrlBuilder() + ); } - return md5($request['data'] . $this->projectPassword) === $request['ss1']; + return $this->requestBuilder; } -} -/** - * Checks SS2 signature. Depends on SSL functions - */ -class WebToPay_Sign_SS2SignChecker implements WebToPay_Sign_SignCheckerInterface { + public function getUrlBuilder(): WebToPay_UrlBuilder + { + if ($this->urlBuilder === null) { + $this->urlBuilder = new WebToPay_UrlBuilder( + $this->configuration, + $this->environment + ); + } + + return $this->urlBuilder; + } /** - * @var string + * Creates or gets SMS answer sender instance + * + * @throws WebToPay_Exception_Configuration */ - protected $publicKey; + public function getSmsAnswerSender(): WebToPay_SmsAnswerSender + { + if ($this->smsAnswerSender === null) { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration('You have to provide project password'); + } + $this->smsAnswerSender = new WebToPay_SmsAnswerSender( + $this->configuration['password'], + $this->getWebClient(), + $this->getUrlBuilder() + ); + } + + return $this->smsAnswerSender; + } /** - * @var WebToPay_Util + * Creates or gets payment list provider instance + * + * @throws WebToPay_Exception_Configuration */ - protected $util; + public function getPaymentMethodListProvider(): WebToPay_PaymentMethodListProvider + { + if ($this->paymentMethodListProvider === null) { + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + $this->paymentMethodListProvider = new WebToPay_PaymentMethodListProvider( + (int) $this->configuration['projectId'], + $this->getWebClient(), + $this->getUrlBuilder() + ); + } + + return $this->paymentMethodListProvider; + } /** - * Constructs object + * Creates or gets signer instance. Chooses SS2 signer if openssl functions are available, SS1 in other case * - * @param string $publicKey - * @param WebToPay_Util $util + * @throws WebToPay_Exception_Configuration + * @throws WebToPayException */ - public function __construct($publicKey, WebToPay_Util $util) { - $this->publicKey = $publicKey; - $this->util = $util; + protected function getSigner(): WebToPay_Sign_SignCheckerInterface + { + if ($this->signer === null) { + if (function_exists('openssl_pkey_get_public')) { + $webClient = $this->getWebClient(); + $publicKey = $webClient->get($this->getUrlBuilder()->buildForPublicKey()); + if (!$publicKey) { + throw new WebToPayException('Cannot download public key from WebToPay website'); + } + $this->signer = new WebToPay_Sign_SS2SignChecker($publicKey, $this->getUtil()); + } else { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration( + 'You have to provide project password if OpenSSL is unavailable' + ); + } + $this->signer = new WebToPay_Sign_SS1SignChecker($this->configuration['password']); + } + } + + return $this->signer; } /** - * Checks signature - * - * @param array $request - * - * @return boolean + * Creates or gets web client instance * - * @throws WebToPay_Exception_Callback + * @throws WebToPay_Exception_Configuration */ - public function checkSign(array $request) { - if (!isset($request['data']) || !isset($request['ss2'])) { - throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + protected function getWebClient(): WebToPay_WebClient + { + if ($this->webClient === null) { + $this->webClient = new WebToPay_WebClient(); } - $ss2 = $this->util->decodeSafeUrlBase64($request['ss2']); - $ok = openssl_verify($request['data'], $ss2, $this->publicKey); - return $ok === 1; + return $this->webClient; } -} - -/** - * Interface for sign checker - */ -interface WebToPay_Sign_SignCheckerInterface { /** - * Checks whether request is signed properly - * - * @param array $request + * Creates or gets util instance * - * @return boolean + * @throws WebToPay_Exception_Configuration */ - public function checkSign(array $request); + protected function getUtil(): WebToPay_Util + { + if ($this->util === null) { + $this->util = new WebToPay_Util(); + } + + return $this->util; + } } @@ -2103,30 +1834,27 @@ public function checkSign(array $request); * * Class WebToPay_UrlBuilder */ -class WebToPay_UrlBuilder { - - const PLACEHOLDER_KEY = '[domain]'; +class WebToPay_UrlBuilder +{ + public const PLACEHOLDER_KEY = '[domain]'; /** - * @var array + * @var array */ - protected $configuration = array(); + protected array $configuration; - /** - * @var string - */ - protected $environment; + protected string $environment; /** - * @var array + * @var array */ - protected $environmentSettings; + protected array $environmentSettings; /** - * @param array $configuration + * @param array $configuration * @param string $environment */ - function __construct($configuration, $environment) + public function __construct(array $configuration, string $environment) { $this->configuration = $configuration; $this->environment = $environment; @@ -2136,283 +1864,440 @@ function __construct($configuration, $environment) /** * Builds a complete request URL based on the provided parameters * - * @param $request - * @param null $language + * @param array $request + * @param string|null $language + * * @return string */ - public function buildForRequest($request, $language = null) { + public function buildForRequest(array $request, ?string $language = null): string + { return $this->createUrlFromRequestAndLanguage($request); } /** * Builds a complete URL for payment list API - * - * @param int $projectId - * @param string $amount - * @param string $currency - * @return string */ - public function buildForPaymentsMethodList($projectId, $amount, $currency) { + public function buildForPaymentsMethodList(int $projectId, string $amount, string $currency): string + { $route = $this->environmentSettings['paymentMethodList']; + return $route . $projectId . '/currency:' . $currency . '/amount:' . $amount; } /** * Builds a complete URL for Sms Answer - * - * @return string */ - public function buildForSmsAnswer() { - $route = $this->environmentSettings['smsAnswer']; - return $route; + public function buildForSmsAnswer(): string + { + return $this->environmentSettings['smsAnswer']; } /** - * Build the url to the public key - * - * @return string + * Build the URL to the public key */ - public function buildForPublicKey() { - $route = $this->environmentSettings['publicKey']; - return $route; + public function buildForPublicKey(): string + { + return $this->environmentSettings['publicKey']; } /** - * Creates an URL from the request and data provided. + * Creates a URL from the request and data provided. + * + * @param array $request * - * @param array $request * @return string */ - protected function createUrlFromRequestAndLanguage($request) { + protected function createUrlFromRequestAndLanguage(array $request): string + { $url = $this->getPaymentUrl() . '?' . http_build_query($request, '', '&'); - return preg_replace('/[\r\n]+/is', '', $url); + + return preg_replace('/[\r\n]+/is', '', $url) ?? ''; } /** - * Returns payment url. Argument is same as lang parameter in request data + * Returns payment URL. Argument is same as lang parameter in request data * - * @return string $url + * @return string */ - public function getPaymentUrl() { - $route = $this->environmentSettings['payment']; - return $route; + public function getPaymentUrl(): string + { + return $this->environmentSettings['payment']; } } /** - * Payment method configuration for some country + * Wrapper class to group payment methods. Each country can have several payment method groups, each of them + * have one or more payment methods. */ -class WebToPay_PaymentMethodCountry { +class WebToPay_PaymentMethodGroup +{ /** - * @var string + * Some unique (in the scope of country) key for this group */ - protected $countryCode; + protected string $groupKey; /** - * Holds available payment types for this country + * Translations array for this group. Holds associative array of group title by country codes. * - * @var WebToPay_PaymentMethodGroup[] + * @var array */ - protected $groups; + protected array $translations; /** - * Default language for titles + * Holds actual payment methods * - * @var string + * @var WebToPay_PaymentMethod[] */ - protected $defaultLanguage; + protected array $paymentMethods; /** - * Translations array for this country. Holds associative array of country title by language codes. - * - * @var array + * Default language for titles */ - protected $titleTranslations; + protected string $defaultLanguage; /** * Constructs object * - * @param string $countryCode - * @param array $titleTranslations + * @param string $groupKey + * @param array $translations * @param string $defaultLanguage */ - public function __construct($countryCode, $titleTranslations, $defaultLanguage = 'lt') { - $this->countryCode = $countryCode; + public function __construct(string $groupKey, array $translations = [], string $defaultLanguage = 'lt') + { + $this->groupKey = $groupKey; + $this->translations = $translations; $this->defaultLanguage = $defaultLanguage; - $this->titleTranslations = $titleTranslations; - $this->groups = array(); + $this->paymentMethods = []; } /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodCountry */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodGroup + { $this->defaultLanguage = $language; - foreach ($this->groups as $group) { - $group->setDefaultLanguage($language); + foreach ($this->paymentMethods as $paymentMethod) { + $paymentMethod->setDefaultLanguage($language); } + return $this; } + /** + * Gets default language for titles + */ + public function getDefaultLanguage(): string + { + return $this->defaultLanguage; + } + /** * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not * specified, uses default language, given to constructor. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { - if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { - return $this->titleTranslations[$languageCode]; - } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { - return $this->titleTranslations[$this->defaultLanguage]; + public function getTitle(?string $languageCode = null): string + { + if ($languageCode !== null && isset($this->translations[$languageCode])) { + return $this->translations[$languageCode]; + } elseif (isset($this->translations[$this->defaultLanguage])) { + return $this->translations[$this->defaultLanguage]; } else { - return $this->countryCode; + return $this->groupKey; } } /** - * Gets default language for titles - * - * @return string + * Returns group key */ - public function getDefaultLanguage() { - return $this->defaultLanguage; + public function getKey(): string + { + return $this->groupKey; } /** - * Gets country code + * Returns available payment methods for this group * - * @return string + * @return WebToPay_PaymentMethod[] */ - public function getCode() { - return $this->countryCode; + public function getPaymentMethods(): array + { + return $this->paymentMethods; } + /** - * Adds new group to payment methods for this country. - * If some other group was registered earlier with same key, overwrites it. - * Returns given group + * Adds new payment method for this group. + * If some other payment method with specified key was registered earlier, overwrites it. + * Returns given payment method * - * @param WebToPay_PaymentMethodGroup $group + * @param WebToPay_PaymentMethod $paymentMethod * - * @return WebToPay_PaymentMethodGroup + * @return WebToPay_PaymentMethod */ - public function addGroup(WebToPay_PaymentMethodGroup $group) { - return $this->groups[$group->getKey()] = $group; + public function addPaymentMethod(WebToPay_PaymentMethod $paymentMethod): WebToPay_PaymentMethod + { + return $this->paymentMethods[$paymentMethod->getKey()] = $paymentMethod; } /** - * Gets group object with specified group key. If no group with such key is found, returns null. - * - * @param string $groupKey - * - * @return null|WebToPay_PaymentMethodGroup + * Gets payment method object with key. If no payment method with such key is found, returns null. */ - public function getGroup($groupKey) { - return isset($this->groups[$groupKey]) ? $this->groups[$groupKey] : null; + public function getPaymentMethod(string $key): ?WebToPay_PaymentMethod + { + return $this->paymentMethods[$key] ?? null; } /** - * Returns payment method groups registered for this country. + * Returns new group instance with only those payment methods, which are available for provided amount. * - * @return WebToPay_PaymentMethodGroup[] + * @throws WebToPayException */ - public function getGroups() { - return $this->groups; + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodGroup + { + $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); + foreach ($this->getPaymentMethods() as $paymentMethod) { + if ($paymentMethod->isAvailableForAmount($amount, $currency)) { + $group->addPaymentMethod($paymentMethod); + } + } + + return $group; } /** - * Gets payment methods in all groups - * - * @return WebToPay_PaymentMethod[] + * Returns new country instance with only those payment methods, which are returns or not iban number after payment */ - public function getPaymentMethods() { - $paymentMethods = array(); - foreach ($this->groups as $group) { - $paymentMethods = array_merge($paymentMethods, $group->getPaymentMethods()); + public function filterForIban(bool $isIban = true): WebToPay_PaymentMethodGroup + { + $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); + foreach ($this->getPaymentMethods() as $paymentMethod) { + if ($paymentMethod->isIban() == $isIban) { + $group->addPaymentMethod($paymentMethod); + } } - return $paymentMethods; + + return $group; } /** - * Returns new country instance with only those payment methods, which are available for provided amount. - * - * @param integer $amount - * @param string $currency + * Returns whether this group has no payment methods * - * @return WebToPay_PaymentMethodCountry + * @return bool */ - public function filterForAmount($amount, $currency) { - $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); - foreach ($this->getGroups() as $group) { - $group = $group->filterForAmount($amount, $currency); - if (!$group->isEmpty()) { - $country->addGroup($group); + public function isEmpty(): bool + { + return count($this->paymentMethods) === 0; + } + + /** + * Loads payment methods from given XML node + */ + public function fromXmlNode(SimpleXMLElement $groupNode): void + { + foreach ($groupNode->payment_type as $paymentTypeNode) { + $key = (string)$paymentTypeNode->attributes()->key; + $titleTranslations = []; + foreach ($paymentTypeNode->title as $titleNode) { + $titleTranslations[(string)$titleNode->attributes()->language] = (string)$titleNode; + } + $logoTranslations = []; + foreach ($paymentTypeNode->logo_url as $logoNode) { + if ((string)$logoNode !== '') { + $logoTranslations[(string)$logoNode->attributes()->language] = (string)$logoNode; + } + } + $minAmount = null; + $maxAmount = null; + $currency = null; + $isIban = false; + $baseCurrency = null; + if (isset($paymentTypeNode->min)) { + $minAmount = (int)$paymentTypeNode->min->attributes()->amount; + $currency = (string)$paymentTypeNode->min->attributes()->currency; + } + if (isset($paymentTypeNode->max)) { + $maxAmount = (int)$paymentTypeNode->max->attributes()->amount; + $currency = (string)$paymentTypeNode->max->attributes()->currency; + } + + if (isset($paymentTypeNode->is_iban)) { + $isIban = (bool)$paymentTypeNode->is_iban; + } + if (isset($paymentTypeNode->base_currency)) { + $baseCurrency = (string)$paymentTypeNode->base_currency; } + $this->addPaymentMethod($this->createPaymentMethod( + $key, + $minAmount, + $maxAmount, + $currency, + $logoTranslations, + $titleTranslations, + $isIban, + $baseCurrency + )); } - return $country; } /** - * Returns new country instance with only those payment methods, which are returns or not iban number after payment + * Method to create new payment method instances. Overwrite if you have to use some other subclass. * - * @param boolean $isIban + * @param string $key + * @param int|null $minAmount + * @param int|null $maxAmount + * @param string|null $currency + * @param array $logoList + * @param array $titleTranslations + * @param bool $isIban + * @param mixed $baseCurrency * - * @return WebToPay_PaymentMethodCountry + * @return WebToPay_PaymentMethod */ - public function filterForIban($isIban = true) { - $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); - foreach ($this->getGroups() as $group) { - $group = $group->filterForIban($isIban); - if (!$group->isEmpty()) { - $country->addGroup($group); - } - } - return $country; + protected function createPaymentMethod( + string $key, + ?int $minAmount, + ?int $maxAmount, + ?string $currency, + array $logoList = [], + array $titleTranslations = [], + bool $isIban = false, + $baseCurrency = null + ): WebToPay_PaymentMethod { + return new WebToPay_PaymentMethod( + $key, + $minAmount, + $maxAmount, + $currency, + $logoList, + $titleTranslations, + $this->defaultLanguage, + $isIban, + $baseCurrency + ); } +} + + +/** + * Parses and validates callbacks + */ +class WebToPay_CallbackValidator +{ + protected WebToPay_Sign_SignCheckerInterface $signer; + + protected WebToPay_Util $util; + + protected int $projectId; + + protected ?string $password; /** - * Returns whether this country has no groups + * Constructs object * - * @return boolean + * @param integer $projectId + * @param WebToPay_Sign_SignCheckerInterface $signer + * @param WebToPay_Util $util + * @param string|null $password */ - public function isEmpty() { - return count($this->groups) === 0; + public function __construct( + int $projectId, + WebToPay_Sign_SignCheckerInterface $signer, + WebToPay_Util $util, + ?string $password = null + ) { + $this->signer = $signer; + $this->util = $util; + $this->projectId = $projectId; + $this->password = $password; } /** - * Loads groups from given XML node + * Parses callback parameters from query parameters and checks if sign is correct. + * Request has parameter "data", which is signed and holds all callback parameters + * + * @param array $requestData + * + * @return array Parsed callback parameters * - * @param SimpleXMLElement $countryNode + * @throws WebToPayException + * @throws WebToPay_Exception_Callback */ - public function fromXmlNode($countryNode) { - foreach ($countryNode->payment_group as $groupNode) { - $key = (string) $groupNode->attributes()->key; - $titleTranslations = array(); - foreach ($groupNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + public function validateAndParseData(array $requestData): array + { + if (!isset($requestData['data'])) { + throw new WebToPay_Exception_Callback('"data" parameter not found'); + } + + $data = $requestData['data']; + + if (isset($requestData['ss1']) || isset($requestData['ss2'])) { + if (!$this->signer->checkSign($requestData)) { + throw new WebToPay_Exception_Callback('Invalid sign parameters, check $_GET length limit'); } - $this->addGroup($this->createGroup($key, $titleTranslations))->fromXmlNode($groupNode); + + $queryString = $this->util->decodeSafeUrlBase64($data); + } else { + if (null === $this->password) { + throw new WebToPay_Exception_Configuration('You have to provide project password'); + } + + $queryString = $this->util->decryptGCM( + $this->util->decodeSafeUrlBase64($data), + $this->password + ); + + if (null === $queryString) { + throw new WebToPay_Exception_Callback('Callback data decryption failed'); + } + } + $request = $this->util->parseHttpQuery($queryString); + + if (!isset($request['projectid'])) { + throw new WebToPay_Exception_Callback( + 'Project ID not provided in callback', + WebToPayException::E_INVALID + ); } + + if ((string) $request['projectid'] !== (string) $this->projectId) { + throw new WebToPay_Exception_Callback( + sprintf('Bad projectid: %s, should be: %s', $request['projectid'], $this->projectId), + WebToPayException::E_INVALID + ); + } + + if (!isset($request['type']) || !in_array($request['type'], ['micro', 'macro'], true)) { + $micro = ( + isset($request['to']) + && isset($request['from']) + && isset($request['sms']) + ); + $request['type'] = $micro ? 'micro' : 'macro'; + } + + return $request; } /** - * Method to create new group instances. Overwrite if you have to use some other group subtype. + * Checks data to have all the same parameters provided in expected array * - * @param string $groupKey - * @param array $translations + * @param array $data + * @param array $expected * - * @return WebToPay_PaymentMethodGroup + * @throws WebToPayException */ - protected function createGroup($groupKey, array $translations = array()) { - return new WebToPay_PaymentMethodGroup($groupKey, $translations, $this->defaultLanguage); + public function checkExpectedFields(array $data, array $expected): void + { + foreach ($expected as $key => $value) { + $passedValue = $data[$key] ?? null; + // there should be non-strict comparison here + if ($passedValue != $value) { + throw new WebToPayException( + sprintf('Field %s is not as expected (expected %s, got %s)', $key, $value, $passedValue) + ); + } + } } -} \ No newline at end of file +} diff --git a/build/build.xml b/build/build.xml index d0e19e9..5922ea3 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1,7 +1,7 @@ - + @@ -24,13 +24,27 @@ - + + + + + + + + + + + + + + + From 24fb9c47cb58620bf34af03bbe009f26f5f40009 Mon Sep 17 00:00:00 2001 From: Oleksandr Gribiennikov Date: Tue, 16 Apr 2024 16:06:39 +0300 Subject: [PATCH 05/23] Add CI/CD actions/rules --- .github/workflows/php-cs-fixer.yml | 30 +++++++++++++++++++++++ .github/workflows/phpstan.yml | 38 ++++++++++++++++++++++++++++++ .github/workflows/run-tests.yml | 36 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 .github/workflows/php-cs-fixer.yml create mode 100644 .github/workflows/phpstan.yml create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml new file mode 100644 index 0000000..5d8ccbd --- /dev/null +++ b/.github/workflows/php-cs-fixer.yml @@ -0,0 +1,30 @@ +name: Check styling + +on: [pull_request] + +permissions: + contents: read + +jobs: + style: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install PHP dependencies + run: composer update --no-cache --prefer-stable --no-interaction --no-progress + + - name: Check styling + run: vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --dry-run --stop-on-violation --using-cache=no --path-mode=intersection ./src ./tests diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..5802ca3 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,38 @@ +name: PHPStan + +on: + pull_request: + paths: + - '**.php' + - 'phpstan.neon.dist' + +permissions: + contents: read + +jobs: + phpstan: + name: phpstan + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install PHP dependencies + uses: ramsey/composer-install@v2 + + - name: Run PHPStan + run: | + vendor/bin/phpstan --error-format=github analyse src/ + exit_code=$? + if [ $exit_code -ne 0 ]; then + echo "PHPStan analysis failed with exit code $exit_code" + exit $exit_code + fi diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..a3e8c3c --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,36 @@ +name: Run tests + +on: [pull_request] + +permissions: + contents: read + +jobs: + php-tests: + runs-on: ubuntu-latest + + strategy: + matrix: + php: [8.3, 8.2, 8.1, 8.0, 7.4] + dependency-version: [prefer-lowest, prefer-stable] + os: [ubuntu-latest] + + name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install PHP dependencies + run: composer update --${{ matrix.dependency-version }} --no-cache --no-interaction --no-progress + + - name: Execute tests + run: composer run phpunit From e826a8ba9df7217c21a09a1c4127f0eb708d5138 Mon Sep 17 00:00:00 2001 From: Oleksandr Gribiennikov Date: Tue, 16 Apr 2024 16:09:17 +0300 Subject: [PATCH 06/23] Add development environment --- Dockerfile | 33 +++++++++++++++++++++++++++++++++ phpunit.xml | 5 +++++ run_tests.sh | 18 ++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 Dockerfile create mode 100755 run_tests.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3dc401b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM php:7.4-fpm-buster + +RUN groupmod -g 1000 www-data && usermod -u 1000 -g 1000 www-data + +ADD https://github.com/mlocati/docker-php-extension-installer/releases/download/2.1.2/install-php-extensions /usr/local/bin/ + +RUN chmod +x /usr/local/bin/install-php-extensions +RUN install-php-extensions \ + xdebug \ + @composer + +RUN echo "\n[PHP]" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "error_reporting=E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "memory_limit=512M" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "upload_max_filesize=16M" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "max_post_size=16M" >> /usr/local/etc/php/conf.d/docker-fpm.ini + +RUN echo "\n[xdebug]" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "zend_extension=xdebug.so" > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.mode=develop,debug,coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.client_discovery_header=\"\"" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.client_host=\"host.docker.internal\"" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.discover_client_host=On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.output_dir = \"/var/log/nginx/xdebug.log\"" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.remote_cookie_expire_time=3600" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.start_with_request=On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.max_nesting_level=512" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + +USER 1000 +WORKDIR /var/www + +RUN rm -rf composer.lock diff --git a/phpunit.xml b/phpunit.xml index 2526d42..c39d689 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -17,6 +17,11 @@ tests + + + ./src + +