├── client-side ├── gopay.js └── gopay.ext.js ├── src ├── Exception │ ├── ValidationException.php │ ├── AuthorizationException.php │ ├── InvalidStateException.php │ ├── GopayException.php │ └── HttpException.php ├── Http │ ├── Io.php │ ├── Http.php │ ├── HttpClient.php │ ├── Curl.php │ ├── Request.php │ └── Response.php ├── Api │ ├── Lists │ │ ├── TargetType.php │ │ ├── Currency.php │ │ ├── RecurrenceState.php │ │ ├── VatRate.php │ │ ├── PaymentType.php │ │ ├── Scope.php │ │ ├── Result.php │ │ ├── Language.php │ │ ├── RecurrenceCycle.php │ │ ├── Format.php │ │ ├── PaymentState.php │ │ ├── PaymentInstrument.php │ │ └── SwiftCode.php │ ├── Entity │ │ ├── AbstractEntity.php │ │ ├── RecurringPayment.php │ │ ├── RecurrentPayment.php │ │ ├── RecurringPaymentFactory.php │ │ ├── Payment.php │ │ ├── PaymentFactory.php │ │ └── RecurrentPaymentFactory.php │ ├── Objects │ │ ├── AbstractObject.php │ │ ├── Parameter.php │ │ ├── Target.php │ │ ├── Recurrence.php │ │ ├── Payer.php │ │ ├── Contact.php │ │ ├── Item.php │ │ └── Eet.php │ ├── Token.php │ └── Gateway.php ├── Auth │ ├── Auth.php │ └── Oauth2Client.php ├── Bridges │ └── Nette │ │ └── DI │ │ ├── InvalidConfigurationException.php │ │ └── GopayExtension.php ├── Service │ ├── AuthenticationService.php │ ├── AbstractPaymentService.php │ ├── AccountsService.php │ ├── AbstractService.php │ └── PaymentsService.php ├── Utils │ └── Validator.php ├── Config.php └── Client.php ├── .github ├── .kodiak.toml └── workflows │ ├── codesniffer.yml │ ├── phpstan.yml │ └── tests.yml ├── examples ├── verify.php ├── authenticate.php ├── modes.php └── create.php ├── composer.json ├── LICENSE └── phpstan-baseline.neon /client-side/gopay.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stand-alone GoPay 3 | */ 4 | (function ($, undefined) { 5 | 6 | var Gopay = Gopay || {}; 7 | 8 | })(jQuery); 9 | -------------------------------------------------------------------------------- /client-side/gopay.ext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GoPay for nette.ajax.js 3 | */ 4 | (function ($, undefined) { 5 | 6 | $.nette.ext('gopay', {}); 7 | 8 | })(jQuery); 9 | -------------------------------------------------------------------------------- /src/Exception/ValidationException.php: -------------------------------------------------------------------------------- 1 | payments->verify($paymentId); 19 | 20 | var_dump($response); 21 | -------------------------------------------------------------------------------- /src/Service/AuthenticationService.php: -------------------------------------------------------------------------------- 1 | doAuthorization($scope); 15 | } catch (HttpException $e) { 16 | return false; 17 | } 18 | 19 | return true; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Api/Objects/Parameter.php: -------------------------------------------------------------------------------- 1 | $this->name, 25 | 'value' => $this->value, 26 | ]; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/authenticate.php: -------------------------------------------------------------------------------- 1 | authenticate(['scope' => Scope::PAYMENT_CREATE]); 22 | 23 | var_dump($token); 24 | -------------------------------------------------------------------------------- /src/Api/Lists/Format.php: -------------------------------------------------------------------------------- 1 | $this->type, 27 | 'goid' => $this->goid, 28 | ]; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Api/Objects/Recurrence.php: -------------------------------------------------------------------------------- 1 | $this->cycle, 28 | 'recurrence_period' => $this->period, 29 | 'recurrence_date_to' => $this->dateTo, 30 | ]; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/Service/AbstractPaymentService.php: -------------------------------------------------------------------------------- 1 | hasTarget()) { 18 | $target = new Target(); 19 | $target->goid = $this->client->getGoId(); 20 | $target->type = TargetType::ACCOUNT; 21 | 22 | $payment->setTarget($target); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/phpstan.yml: -------------------------------------------------------------------------------- 1 | name: "Phpstan" 2 | 3 | on: 4 | pull_request: 5 | 6 | push: 7 | branches: ["*"] 8 | 9 | schedule: 10 | - cron: "0 8 * * 1" 11 | 12 | jobs: 13 | phpstan81: 14 | name: "Phpstan" 15 | uses: contributte/.github/.github/workflows/phpstan.yml@v1 16 | with: 17 | php: "8.1" 18 | 19 | 20 | phpstan80: 21 | name: "Phpstan" 22 | uses: contributte/.github/.github/workflows/phpstan.yml@v1 23 | with: 24 | php: "8.0" 25 | 26 | lower: 27 | name: "Nette Tester" 28 | uses: contributte/.github/.github/workflows/phpstan.yml@v1 29 | with: 30 | php: "7.2" 31 | composer: "composer update --no-interaction --no-progress --prefer-dist --prefer-stable --prefer-lowest" 32 | -------------------------------------------------------------------------------- /src/Service/AccountsService.php: -------------------------------------------------------------------------------- 1 | $date_from, 18 | 'date_to' => $date_to, 19 | 'currency' => $currency, 20 | 'format' => $format, 21 | 'goid' => $this->client->getGoId(), 22 | ]; 23 | 24 | return $this->makeRequest('POST', 'accounts/account-statement', $data); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: "Nette Tester" 2 | 3 | on: 4 | pull_request: 5 | 6 | push: 7 | branches: [ "*" ] 8 | 9 | schedule: 10 | - cron: "0 8 * * 1" 11 | 12 | jobs: 13 | test81: 14 | name: "Nette Tester" 15 | uses: contributte/.github/.github/workflows/nette-tester.yml@v1 16 | with: 17 | php: "8.1" 18 | 19 | test80: 20 | name: "Nette Tester" 21 | uses: contributte/.github/.github/workflows/nette-tester.yml@v1 22 | with: 23 | php: "8.0" 24 | 25 | lower: 26 | name: "Nette Tester" 27 | uses: contributte/.github/.github/workflows/nette-tester.yml@v1 28 | with: 29 | php: "7.2" 30 | composer: "composer update --no-interaction --no-progress --prefer-dist --prefer-stable --prefer-lowest" 31 | -------------------------------------------------------------------------------- /src/Api/Lists/PaymentState.php: -------------------------------------------------------------------------------- 1 | getAmountInCents(); 16 | $data['currency'] = $this->getCurrency(); 17 | 18 | $data['order_number'] = $this->getOrderNumber(); 19 | $data['order_description'] = $this->getOrderDescription(); 20 | 21 | $data['items'] = $this->formatItems($this->getItems()); 22 | 23 | $parameters = $this->getParameters(); 24 | if (count($parameters) > 0) { 25 | $data['additional_params'] = $this->formatParameters($parameters); 26 | } 27 | 28 | return $data; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Utils/Validator.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | public static function validateRequired(array $array, array $keys) 14 | { 15 | $diff = array_diff_key(array_flip($keys), $array); 16 | 17 | return count($diff) > 0 ? array_keys($diff) : true; 18 | } 19 | 20 | /** 21 | * @param mixed[] $array 22 | * @param string[] $keys 23 | * @return true|array 24 | */ 25 | public static function validateOptional(array $array, array $keys) 26 | { 27 | $diff = array_diff_key($array, array_flip($keys)); 28 | 29 | return count($diff) > 0 ? array_keys($diff) : true; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Exception/HttpException.php: -------------------------------------------------------------------------------- 1 | field) ? '[' . $error->field . ']' : null; 13 | $description = $error->description ?? null; 14 | $message = isset($error->message) ? rtrim($error->message, '.') . ($description !== null ? ':' : '') : null; 15 | $scope = isset($error->scope) ? '(' . $error->scope . ')' : null; 16 | $code = isset($error->error_code) ? '#' . $error->error_code : null; 17 | 18 | $parts = array_filter([$code, $scope, $field, $message, $description], function ($item) { 19 | return $item !== null; 20 | }); 21 | 22 | return implode(' ', $parts); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /examples/modes.php: -------------------------------------------------------------------------------- 1 | recurrence; 16 | } 17 | 18 | public function setRecurrence(Recurrence $recurrence): void 19 | { 20 | $this->recurrence = $recurrence; 21 | } 22 | 23 | /** 24 | * ABSTRACT **************************************************************** 25 | */ 26 | 27 | /** 28 | * @return mixed[] 29 | */ 30 | public function toArray(): array 31 | { 32 | $payment = parent::toArray(); 33 | 34 | if ($this->recurrence !== null) { 35 | $payment['recurrence'] = $this->recurrence->toArray(); 36 | } 37 | 38 | return $payment; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/Api/Token.php: -------------------------------------------------------------------------------- 1 | type = $data['token_type']; 28 | } 29 | 30 | if (isset($data['access_token'])) { 31 | $token->accessToken = $data['access_token']; 32 | } 33 | 34 | if (isset($data['refresh_token'])) { 35 | $token->refreshToken = $data['refresh_token']; 36 | } 37 | 38 | if (isset($data['expires_in'])) { 39 | $token->expireIn = $data['expires_in']; 40 | } 41 | 42 | return $token; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/Http/HttpClient.php: -------------------------------------------------------------------------------- 1 | io === null) { 16 | $this->io = new Curl(); 17 | } 18 | 19 | return $this->io; 20 | } 21 | 22 | public function setIo(Io $io): void 23 | { 24 | $this->io = $io; 25 | } 26 | 27 | /** 28 | * API ********************************************************************* 29 | */ 30 | 31 | /** 32 | * Take request and execute him 33 | */ 34 | public function doRequest(Request $request): Response 35 | { 36 | $response = $this->getIo()->call($request); 37 | if (!$response->isSuccess()) { 38 | // cURL error 39 | throw new HttpException('Request failed'); 40 | } 41 | 42 | if (isset($response->data['errors'])) { 43 | // GoPay errors 44 | $error = $response->data['errors'][0]; 45 | throw new HttpException(HttpException::format($error), $error->error_code); 46 | } 47 | 48 | return $response; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/Api/Gateway.php: -------------------------------------------------------------------------------- 1 | allowedPaymentInstruments !== null) { 35 | $data['allowed_payment_instruments'] = $this->allowedPaymentInstruments; 36 | } 37 | 38 | if ($this->defaultPaymentInstrument !== null) { 39 | $data['default_payment_instrument'] = $this->defaultPaymentInstrument; 40 | } 41 | 42 | if ($this->defaultSwift !== null) { 43 | $data['default_swift'] = $this->defaultSwift; 44 | } 45 | 46 | if ($this->allowedSwifts !== null) { 47 | $data['allowed_swifts'] = $this->allowedSwifts; 48 | } 49 | 50 | if ($this->contact !== null) { 51 | $data['contact'] = $this->contact->toArray(); 52 | } 53 | 54 | return $data; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | goId = $goId; 29 | $this->clientId = $clientId; 30 | $this->clientSecret = $clientSecret; 31 | $this->setMode($mode); 32 | } 33 | 34 | public function getGoId(): string 35 | { 36 | return $this->goId; 37 | } 38 | 39 | public function getClientId(): string 40 | { 41 | return $this->clientId; 42 | } 43 | 44 | public function getClientSecret(): string 45 | { 46 | return $this->clientSecret; 47 | } 48 | 49 | public function getMode(): string 50 | { 51 | return $this->mode; 52 | } 53 | 54 | public function setMode(string $mode): void 55 | { 56 | if ($mode === self::PROD) { 57 | Gateway::init(Gateway::PROD); 58 | $this->mode = self::PROD; 59 | } else { 60 | Gateway::init(Gateway::TEST); 61 | $this->mode = self::TEST; 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Http/Curl.php: -------------------------------------------------------------------------------- 1 | getUrl()); 15 | 16 | // Set-up headers 17 | $headers = $request->getHeaders(); 18 | array_walk($headers, function (&$item, $key): void { 19 | $item = sprintf('%s:%s', $key, $item); 20 | }); 21 | curl_setopt($ch, CURLOPT_HTTPHEADER, array_values($headers)); 22 | 23 | // Set-up others 24 | curl_setopt_array($ch, $request->getOpts()); 25 | 26 | // Receive result 27 | $result = curl_exec($ch); 28 | 29 | // Parse response 30 | $response = new Response(); 31 | if ($result === false) { 32 | $response->setError(curl_strerror(curl_errno($ch))); 33 | $response->setData([]); 34 | $response->setCode(curl_errno($ch)); 35 | $response->setHeaders(curl_getinfo($ch)); 36 | } else { 37 | $info = curl_getinfo($ch); 38 | $response->setCode(curl_getinfo($ch, CURLINFO_HTTP_CODE)); 39 | $response->setHeaders($info); 40 | 41 | if ($info['content_type'] === 'application/octet-stream') { 42 | $response->setData($result); 43 | } else { 44 | $response->setData(json_decode((string) $result)); 45 | } 46 | } 47 | 48 | // Close cURL 49 | curl_close($ch); 50 | 51 | return $response; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markette/gopay-inline", 3 | "description": "GoPay Inline Payment Gateway", 4 | "keywords": [ 5 | "eshop", 6 | "payment", 7 | "gopay", 8 | "api", 9 | "inline" 10 | ], 11 | "homepage": "https://github.com/contributte/gopay-inline", 12 | "license": "BSD-3-Clause", 13 | "authors": [ 14 | { 15 | "name": "Milan Felix Šulc", 16 | "email": "sulcmil@gmail.com" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=7.2", 21 | "ext-curl": "*", 22 | "ext-json": "*", 23 | "moneyphp/money": "^3.0|^4.0" 24 | }, 25 | "require-dev": { 26 | "ninjify/qa": "^0.14", 27 | "ninjify/nunjuck": "^0.3", 28 | "mockery/mockery": "^1.3.3", 29 | "phpstan/phpstan": "^1.8.5", 30 | "phpstan/phpstan-strict-rules": "^1.4.3", 31 | "phpstan/phpstan-nette": "^1.1.0", 32 | "phpstan/phpstan-deprecation-rules": "^1.1.0", 33 | "nette/di": "^3.0.0", 34 | "nette/utils": "^3.0.0" 35 | }, 36 | "suggest": { 37 | "nette/di": "For GopayExtension" 38 | }, 39 | "conflict": { 40 | "nette/di": "<3.0" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "Contributte\\GopayInline\\": "src/" 45 | } 46 | }, 47 | "prefer-stable": true, 48 | "minimum-stability": "dev", 49 | "extra": { 50 | "branch-alias": { 51 | "dev-master": "2.2.x-dev" 52 | } 53 | }, 54 | "config": { 55 | "allow-plugins": { 56 | "dealerdirect/phpcodesniffer-composer-installer": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Contributte 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of gopay-inline nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /src/Api/Lists/SwiftCode.php: -------------------------------------------------------------------------------- 1 | firstname !== null) { 44 | $data['first_name'] = $this->firstname; 45 | } 46 | 47 | if ($this->lastname !== null) { 48 | $data['last_name'] = $this->lastname; 49 | } 50 | 51 | if ($this->email !== null) { 52 | $data['email'] = $this->email; 53 | } 54 | 55 | if ($this->phone !== null) { 56 | $data['phone_number'] = $this->phone; 57 | } 58 | 59 | if ($this->city !== null) { 60 | $data['city'] = $this->city; 61 | } 62 | 63 | if ($this->street !== null) { 64 | $data['street'] = $this->street; 65 | } 66 | 67 | if ($this->zip !== null) { 68 | $data['postal_code'] = $this->zip; 69 | } 70 | 71 | if ($this->country !== null) { 72 | $data['country_code'] = $this->country; 73 | } 74 | 75 | return $data; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Http/Request.php: -------------------------------------------------------------------------------- 1 | url; 23 | } 24 | 25 | public function setUrl(string $url): void 26 | { 27 | $this->url = $url; 28 | } 29 | 30 | /** 31 | * @return mixed[] 32 | */ 33 | public function getHeaders(): array 34 | { 35 | return $this->headers; 36 | } 37 | 38 | /** 39 | * @param mixed[] $headers 40 | */ 41 | public function setHeaders(array $headers): void 42 | { 43 | $this->headers = $headers; 44 | } 45 | 46 | public function addHeader(string $name, string $value): void 47 | { 48 | $this->headers[$name] = $value; 49 | } 50 | 51 | /** 52 | * @param mixed[] $headers 53 | */ 54 | public function appendHeaders(array $headers): void 55 | { 56 | $this->headers += $headers; 57 | } 58 | 59 | /** 60 | * @return mixed[] 61 | */ 62 | public function getOpts(): array 63 | { 64 | return $this->opts; 65 | } 66 | 67 | /** 68 | * @param mixed[] $opts 69 | */ 70 | public function setOpts(array $opts): void 71 | { 72 | $this->opts = $opts; 73 | } 74 | 75 | public function addOpt(string $name, string $value): void 76 | { 77 | $this->opts[$name] = $value; 78 | } 79 | 80 | /** 81 | * @param mixed[] $opts 82 | */ 83 | public function appendOpts(array $opts): void 84 | { 85 | $this->opts += $opts; 86 | } 87 | 88 | /** 89 | * @return mixed[] 90 | */ 91 | public function getData(): array 92 | { 93 | return $this->data; 94 | } 95 | 96 | /** 97 | * @param mixed[] $data 98 | */ 99 | public function setData(array $data): void 100 | { 101 | $this->data = $data; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/Bridges/Nette/DI/GopayExtension.php: -------------------------------------------------------------------------------- 1 | getConfig(); 24 | $builder = $this->getContainerBuilder(); 25 | 26 | if (!class_exists(Schema::class)) { 27 | $this->validate($config); 28 | } 29 | 30 | $builder->addDefinition($this->prefix('client')) 31 | ->setFactory(Client::class, [ 32 | new Statement(Config::class, [ 33 | $config->goId, 34 | $config->clientId, 35 | $config->clientSecret, 36 | $config->test !== false ? Config::TEST : Config::PROD, 37 | ]), 38 | ]); 39 | } 40 | 41 | public function getConfigSchema(): Schema 42 | { 43 | return Expect::structure([ 44 | 'goId' => Expect::anyOf(new Type('string'), new Type('int'))->required(), 45 | 'clientId' => Expect::anyOf(new Type('string'), new Type('int'))->required(), 46 | 'clientSecret' => Expect::string()->required(), 47 | 'test' => Expect::bool(true), 48 | ]); 49 | } 50 | 51 | private function validate(stdClass $config): void 52 | { 53 | if (!isset($config->goId)) { 54 | throw new InvalidConfigurationException(sprintf('Missing %s.goId configuration option.', $this->name)); 55 | } 56 | 57 | if (!isset($config->clientId)) { 58 | throw new InvalidConfigurationException(sprintf('Missing %s.clientId configuration option.', $this->name)); 59 | } 60 | 61 | if (!isset($config->clientSecret)) { 62 | throw new InvalidConfigurationException(sprintf('Missing %s.clientSecret configuration option.', $this->name)); 63 | } 64 | 65 | if (!isset($config->test)) { 66 | $config->test = true; 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Auth/Oauth2Client.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | $this->http = $http; 25 | } 26 | 27 | /** 28 | * @param mixed[] $credentials 29 | */ 30 | public function authenticate(array $credentials): Response 31 | { 32 | $request = new Request(); 33 | 34 | // Set URL 35 | $request->setUrl(Gateway::getOauth2TokenUrl()); 36 | 37 | // Prepare data 38 | $args = [ 39 | 'grant_type' => 'client_credentials', 40 | 'scope' => $credentials['scope'], 41 | ]; 42 | $data = http_build_query($args); 43 | 44 | // Set-up headers 45 | $headers = [ 46 | 'Accept' => 'application/json', 47 | 'Content-Type' => 'application/x-www-form-urlencoded', 48 | ]; 49 | $request->setHeaders($headers); 50 | 51 | // Set-up opts 52 | $opts = [ 53 | CURLOPT_SSL_VERIFYPEER => false, 54 | CURLOPT_POST => true, 55 | CURLOPT_RETURNTRANSFER => true, 56 | CURLOPT_USERPWD => $this->client->getClientId() . ':' . $this->client->getClientSecret(), 57 | CURLOPT_POSTFIELDS => $data, 58 | ]; 59 | $request->setOpts($opts); 60 | 61 | // Make request 62 | $response = $this->http->doRequest($request); 63 | 64 | if ($response->getData() === null) { 65 | // cURL errors 66 | throw new AuthorizationException('Authorization failed', (int) $response->getCode()); 67 | } 68 | 69 | if (isset($response->getData()['errors'])) { 70 | // GoPay errors 71 | $error = $response->getData()['errors'][0]; 72 | throw new AuthorizationException(AuthorizationException::format($error), $error->error_code); 73 | } 74 | 75 | return $response; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Api/Objects/Item.php: -------------------------------------------------------------------------------- 1 | name; 28 | } 29 | 30 | public function getAmount(): Money 31 | { 32 | return $this->amount; 33 | } 34 | 35 | public function getCount(): int 36 | { 37 | return $this->count; 38 | } 39 | 40 | public function getAmountInCents(): string 41 | { 42 | return $this->amount->getAmount(); 43 | } 44 | 45 | public function getType(): ?string 46 | { 47 | return $this->type; 48 | } 49 | 50 | public function getVatRate(): ?int 51 | { 52 | return $this->vatRate; 53 | } 54 | 55 | public function setName(string $name): void 56 | { 57 | $this->name = $name; 58 | } 59 | 60 | public function setAmount(Money $amount): void 61 | { 62 | $this->amount = $amount; 63 | } 64 | 65 | public function setCount(int $count): void 66 | { 67 | $this->count = $count; 68 | } 69 | 70 | public function setType(string $type): void 71 | { 72 | $this->type = $type; 73 | } 74 | 75 | public function setVatRate(int $vatRate): void 76 | { 77 | $this->vatRate = $vatRate; 78 | } 79 | 80 | /** 81 | * ABSTRACT **************************************************************** 82 | */ 83 | 84 | /** 85 | * @return mixed[] 86 | */ 87 | public function toArray(): array 88 | { 89 | $data = []; 90 | $data['name'] = $this->getName(); 91 | $data['amount'] = $this->getAmountInCents(); 92 | $data['count'] = $this->getCount(); 93 | 94 | // NOT REQUIRED ==================================== 95 | 96 | $type = $this->getType(); 97 | if ($type !== null) { 98 | $data['type'] = $type; 99 | } 100 | 101 | $vatRate = $this->getVatRate(); 102 | if ($vatRate !== null && $vatRate > 0) { 103 | $data['vat_rate'] = $vatRate; 104 | } 105 | 106 | return $data; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /examples/create.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'default_payment_instrument' => PaymentInstrument::BANK_ACCOUNT, 25 | 'allowed_payment_instruments' => [PaymentInstrument::BANK_ACCOUNT], 26 | 'default_swift' => SwiftCode::FIO_BANKA, 27 | 'allowed_swifts' => [SwiftCode::FIO_BANKA, SwiftCode::MBANK], 28 | 'contact' => [ 29 | 'first_name' => 'John', 30 | 'last_name' => 'Doe', 31 | 'email' => 'johndoe@contributte.org', 32 | 'phone_number' => '+420123456789', 33 | 'city' => 'Prague', 34 | 'street' => 'Contributte 123', 35 | 'postal_code' => '123 45', 36 | 'country_code' => 'CZE', 37 | ], 38 | ], 39 | 'amount' => \Money\Money::CZK(50000), 40 | 'order_number' => '001', 41 | 'order_description' => 'some order', 42 | 'items' => [ 43 | ['name' => 'item01', 'amount' => \Money\Money::CZK(40000)], 44 | ['name' => 'item02', 'amount' => \Money\Money::CZK(13000)], 45 | ['name' => 'item03', 'amount' => \Money\Money::CZK(7000)], 46 | ], 47 | 'eet' => [ 48 | 'celk_trzba' => \Money\Money::CZK(50000), 49 | 'zakl_dan1' => \Money\Money::CZK(35000), 50 | 'dan1' => \Money\Money::CZK(5000), 51 | 'zakl_dan2' => \Money\Money::CZK(8000), 52 | 'dan2' => \Money\Money::CZK(2000), 53 | 'mena' => Currency::CZK, 54 | ], 55 | 'additional_params' => [ 56 | ['name' => 'invoicenumber', 'value' => '2017001'], 57 | ], 58 | 'callback' => [ 59 | 'return_url' => 'http://www.myeshop.cz/api/gopay/return', 60 | 'notify_url' => 'http://www.myeshop.cz/api/gopay/notify', 61 | ], 62 | 'lang' => Language::CZ, 63 | ]; 64 | 65 | // Create payment request 66 | $response = $client->payments->createPayment(PaymentFactory::create($payment)); 67 | 68 | var_dump($response); 69 | -------------------------------------------------------------------------------- /src/Service/AbstractService.php: -------------------------------------------------------------------------------- 1 | false, 30 | CURLOPT_RETURNTRANSFER => true, 31 | ]; 32 | 33 | public function __construct(Client $client) 34 | { 35 | $this->client = $client; 36 | } 37 | 38 | protected function doAuthorization(string $scope = Scope::PAYMENT_ALL): string 39 | { 40 | // Invoke events 41 | $this->trigger('onAuthorization', [$scope]); 42 | 43 | return $this->client->authenticate(['scope' => $scope]); 44 | } 45 | 46 | /** 47 | * Build request and execute him 48 | * 49 | * @param mixed[] $data 50 | */ 51 | protected function makeRequest(string $method, string $uri, ?array $data = null, ?string $contentType = Http::CONTENT_JSON): Response 52 | { 53 | // Invoke events 54 | $this->trigger('onRequest', [$method, $uri, $data]); 55 | 56 | // Verify that client is authenticated 57 | if (!$this->client->hasToken()) { 58 | // Do authorization 59 | $this->doAuthorization(); 60 | } 61 | 62 | $request = new Request(); 63 | 64 | // Set-up URL 65 | $request->setUrl(Gateway::getFullApiUrl($uri)); 66 | 67 | // Set-up headers 68 | /** @var Token $token */ 69 | $token = $this->client->getToken(); 70 | 71 | $headers = [ 72 | 'Accept' => 'application/json', 73 | 'Authorization' => 'Bearer ' . $token->accessToken, 74 | 'Content-Type' => $contentType, 75 | ]; 76 | $request->setHeaders($headers); 77 | 78 | // Set-up opts 79 | $request->setOpts($this->options); 80 | 81 | // Set-up method 82 | switch ($method) { 83 | case HttpClient::METHOD_GET: 84 | $request->appendOpts([ 85 | CURLOPT_HTTPGET => true, 86 | ]); 87 | break; 88 | 89 | case HttpClient::METHOD_POST: 90 | $request->appendOpts([ 91 | CURLOPT_POST => true, 92 | CURLOPT_POSTFIELDS => $contentType === Http::CONTENT_FORM ? http_build_query((array) $data) : json_encode($data), 93 | ]); 94 | break; 95 | 96 | default: 97 | throw new InvalidStateException('Unsupported http method'); 98 | } 99 | 100 | return $this->client->call($request); 101 | } 102 | 103 | /** 104 | * @param mixed[] $data 105 | */ 106 | protected function trigger(string $event, array $data): void 107 | { 108 | foreach ($this->{$event} as $callback) { 109 | call_user_func_array($callback, $data); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/Http/Response.php: -------------------------------------------------------------------------------- 1 | 17 | * @implements IteratorAggregate 18 | */ 19 | class Response implements ArrayAccess, Countable, IteratorAggregate 20 | { 21 | 22 | /** @var mixed[]|null */ 23 | protected $data; 24 | 25 | /** @var array */ 26 | protected $headers = []; 27 | 28 | /** @var int|null */ 29 | protected $code; 30 | 31 | /** @var string|null */ 32 | protected $error; 33 | 34 | /** 35 | * @return mixed[]|null 36 | */ 37 | public function getData(): ?array 38 | { 39 | return $this->data; 40 | } 41 | 42 | /** 43 | * @param mixed $data 44 | */ 45 | public function setData($data): void 46 | { 47 | if ($data !== null) { 48 | $data = (array) $data; 49 | } 50 | 51 | $this->data = $data; 52 | } 53 | 54 | /** 55 | * @return array 56 | */ 57 | public function getHeaders(): array 58 | { 59 | return $this->headers; 60 | } 61 | 62 | /** 63 | * @param mixed[] $headers 64 | */ 65 | public function setHeaders(array $headers): void 66 | { 67 | $this->headers = $headers; 68 | } 69 | 70 | public function getCode(): ?int 71 | { 72 | return $this->code; 73 | } 74 | 75 | public function setCode(int $code): void 76 | { 77 | $this->code = $code; 78 | } 79 | 80 | public function getError(): ?string 81 | { 82 | return $this->error; 83 | } 84 | 85 | public function setError(?string $error): void 86 | { 87 | $this->error = $error; 88 | } 89 | 90 | public function isSuccess(): bool 91 | { 92 | return $this->error === null; 93 | } 94 | 95 | /** 96 | * @param mixed $offset 97 | */ 98 | public function offsetExists($offset): bool 99 | { 100 | return isset($this->data[$offset]); 101 | } 102 | 103 | /** 104 | * @param mixed $offset 105 | * @return mixed 106 | */ 107 | #[ReturnTypeWillChange] 108 | public function offsetGet($offset) 109 | { 110 | if ($this->data === null) { 111 | return null; 112 | } 113 | 114 | return $this->data[$offset]; 115 | } 116 | 117 | /** 118 | * @param mixed $offset 119 | * @param mixed $value 120 | */ 121 | public function offsetSet($offset, $value): void 122 | { 123 | $this->data[$offset] = $value; 124 | } 125 | 126 | /** 127 | * @param mixed $offset 128 | */ 129 | public function offsetUnset($offset): void 130 | { 131 | unset($this->data[$offset]); 132 | } 133 | 134 | public function count(): int 135 | { 136 | return $this->data === null ? 0 : count($this->data); 137 | } 138 | 139 | public function getIterator(): RecursiveArrayIterator 140 | { 141 | return new RecursiveArrayIterator($this->data ?? []); 142 | } 143 | 144 | /** 145 | * MAGIC ******************************************************************* 146 | **/ 147 | 148 | /** 149 | * @return mixed 150 | */ 151 | public function __get(string $name) 152 | { 153 | if (isset($this->$name)) { 154 | return $this->$name; 155 | } 156 | 157 | return $this->offsetGet($name); 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Cannot access offset 'contact' on mixed\\.$#" 5 | count: 2 6 | path: src/Api/Entity/PaymentFactory.php 7 | 8 | - 9 | message: "#^Parameter \\#1 \\$array of static method Contributte\\\\GopayInline\\\\Utils\\\\Validator\\:\\:validateRequired\\(\\) expects array, mixed given\\.$#" 10 | count: 1 11 | path: src/Api/Entity/PaymentFactory.php 12 | 13 | - 14 | message: "#^Parameter \\#3 \\$data of static method Contributte\\\\GopayInline\\\\Api\\\\Entity\\\\PaymentFactory\\:\\:map\\(\\) expects array, mixed given\\.$#" 15 | count: 2 16 | path: src/Api/Entity/PaymentFactory.php 17 | 18 | - 19 | message: "#^Cannot access offset 'contact' on mixed\\.$#" 20 | count: 2 21 | path: src/Api/Entity/RecurrentPaymentFactory.php 22 | 23 | - 24 | message: "#^Parameter \\#1 \\$array of static method Contributte\\\\GopayInline\\\\Utils\\\\Validator\\:\\:validateRequired\\(\\) expects array, mixed given\\.$#" 25 | count: 1 26 | path: src/Api/Entity/RecurrentPaymentFactory.php 27 | 28 | - 29 | message: "#^Parameter \\#3 \\$data of static method Contributte\\\\GopayInline\\\\Api\\\\Entity\\\\RecurrentPaymentFactory\\:\\:map\\(\\) expects array, mixed given\\.$#" 30 | count: 2 31 | path: src/Api/Entity/RecurrentPaymentFactory.php 32 | 33 | - 34 | message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" 35 | count: 1 36 | path: src/Api/Entity/RecurringPaymentFactory.php 37 | 38 | - 39 | message: "#^Parameter \\#1 \\$amount of method Contributte\\\\GopayInline\\\\Api\\\\Entity\\\\Payment\\:\\:setAmount\\(\\) expects Money\\\\Money, mixed given\\.$#" 40 | count: 1 41 | path: src/Api/Entity/RecurringPaymentFactory.php 42 | 43 | - 44 | message: "#^Parameter \\#1 \\$description of method Contributte\\\\GopayInline\\\\Api\\\\Entity\\\\Payment\\:\\:setOrderDescription\\(\\) expects string, mixed given\\.$#" 45 | count: 1 46 | path: src/Api/Entity/RecurringPaymentFactory.php 47 | 48 | - 49 | message: "#^Parameter \\#1 \\$orderNumber of method Contributte\\\\GopayInline\\\\Api\\\\Entity\\\\Payment\\:\\:setOrderNumber\\(\\) expects string, mixed given\\.$#" 50 | count: 1 51 | path: src/Api/Entity/RecurringPaymentFactory.php 52 | 53 | - 54 | message: "#^Parameter \\#3 \\$data of static method Contributte\\\\GopayInline\\\\Api\\\\Entity\\\\RecurringPaymentFactory\\:\\:map\\(\\) expects array, mixed given\\.$#" 55 | count: 1 56 | path: src/Api/Entity/RecurringPaymentFactory.php 57 | 58 | - 59 | message: "#^Property Contributte\\\\GopayInline\\\\Api\\\\Token\\:\\:\\$type \\(string\\) does not accept mixed\\.$#" 60 | count: 1 61 | path: src/Api/Token.php 62 | 63 | - 64 | message: "#^Cannot access offset 0 on mixed\\.$#" 65 | count: 1 66 | path: src/Auth/Oauth2Client.php 67 | 68 | - 69 | message: "#^Cannot access property \\$error_code on mixed\\.$#" 70 | count: 1 71 | path: src/Auth/Oauth2Client.php 72 | 73 | - 74 | message: "#^Parameter \\#1 \\$error of static method Contributte\\\\GopayInline\\\\Exception\\\\HttpException\\:\\:format\\(\\) expects stdClass, mixed given\\.$#" 75 | count: 1 76 | path: src/Auth/Oauth2Client.php 77 | 78 | - 79 | message: "#^Static property Contributte\\\\GopayInline\\\\Client\\:\\:\\$services \\(array\\\\) does not accept array\\\\.$#" 80 | count: 1 81 | path: src/Client.php 82 | 83 | - 84 | message: "#^Parameter \\#3 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" 85 | count: 1 86 | path: src/Http/Curl.php 87 | 88 | - 89 | message: "#^Property Contributte\\\\GopayInline\\\\Http\\\\Response\\:\\:\\$headers \\(array\\\\) does not accept array\\.$#" 90 | count: 1 91 | path: src/Http/Response.php 92 | -------------------------------------------------------------------------------- /src/Service/PaymentsService.php: -------------------------------------------------------------------------------- 1 | makeRequest('GET', 'payments/payment/' . $id, null, Http::CONTENT_FORM); 18 | } 19 | 20 | public function createPayment(Payment $payment): Response 21 | { 22 | // Pre-configure payment 23 | $this->preConfigure($payment); 24 | 25 | // Export payment to array 26 | $data = $payment->toArray(); 27 | 28 | // Make request 29 | return $this->makeRequest('POST', 'payments/payment', $data); 30 | } 31 | 32 | public function createRecurrentPayment(RecurrentPayment $payment): Response 33 | { 34 | // Pre-configure payment 35 | $this->preConfigure($payment); 36 | 37 | // Export payment to array 38 | $data = $payment->toArray(); 39 | 40 | // Make request 41 | return $this->makeRequest('POST', 'payments/payment', $data); 42 | } 43 | 44 | public function createRecurringPayment(string $recurrencePaymentId, RecurringPayment $payment): Response 45 | { 46 | // Export payment to array 47 | $data = $payment->toArray(); 48 | 49 | // Make request 50 | return $this->makeRequest('POST', 'payments/payment/' . $recurrencePaymentId . '/create-recurrence', $data); 51 | } 52 | 53 | public function cancelRecurrentPayment(string $recurrencePaymentId): Response 54 | { 55 | return $this->makeRequest('POST', 'payments/payment/' . $recurrencePaymentId . '/void-recurrence', null, Http::CONTENT_FORM); 56 | } 57 | 58 | /** 59 | * @param int|float $id 60 | * @param mixed[] $items Use in case you need to refund payment with EET 61 | * @param mixed[] $eet Use in case you need to refund payment with EET 62 | */ 63 | public function refundPayment($id, float $amount, ?array $items = null, ?array $eet = null): Response 64 | { 65 | // without EET 66 | if ($items === null || $eet === null) { 67 | return $this->makeRequest('POST', 'payments/payment/' . $id . '/refund', ['amount' => round($amount * 100)], Http::CONTENT_FORM); 68 | } 69 | 70 | // with EET 71 | $data = array_merge( 72 | ['amount' => round($amount * 100)], 73 | ['items' => $items], 74 | ['eet' => $eet] 75 | ); 76 | 77 | return $this->makeRequest('POST', 'payments/payment/' . $id . '/refund', $data, Http::CONTENT_JSON); 78 | } 79 | 80 | /** 81 | * @param int|float $id 82 | * @param float|null $amount 83 | * @param mixed[]|null $items 84 | */ 85 | public function capturePayment($id, ?float $amount = null, ?array $items = null): Response 86 | { 87 | if ($amount === null || $items === null) { 88 | return $this->makeRequest('POST', 'payments/payment/' . $id . '/capture'); 89 | } 90 | 91 | $data = array_merge( 92 | ['amount' => round($amount * 100)], 93 | ['items' => $items] 94 | ); 95 | 96 | return $this->makeRequest('POST', 'payments/payment/' . $id . '/capture', $data); 97 | } 98 | 99 | public function getPaymentInstruments(string $currency): Response 100 | { 101 | // Make request 102 | return $this->makeRequest('GET', 'eshops/eshop/' . $this->client->getGoId() . '/payment-instruments/' . $currency, null, null); 103 | } 104 | 105 | /** 106 | * @param int|float $id ID of payment for which we need list of EET receipts 107 | */ 108 | public function getEetReceipts($id): Response 109 | { 110 | // Make request 111 | return $this->makeRequest('GET', 'payments/payment/' . $id . '/eet-receipts'); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/Api/Entity/RecurringPaymentFactory.php: -------------------------------------------------------------------------------- 1 | */ 33 | public static $validators = [ 34 | self::V_SCHEME => true, 35 | self::V_PRICES => true, 36 | ]; 37 | 38 | /** 39 | * @param mixed $data 40 | * @param mixed[] $validators 41 | */ 42 | public static function create($data, array $validators = []): RecurringPayment 43 | { 44 | // Convert to array 45 | $data = (array) $data; 46 | $validators += self::$validators; 47 | 48 | // CHECK REQUIRED DATA ################### 49 | 50 | $res = Validator::validateRequired($data, self::$required); 51 | if ($res !== true) { 52 | throw new ValidationException('Missing keys "' . (implode(', ', $res)) . '""'); 53 | } 54 | 55 | // CHECK SCHEME DATA ##################### 56 | 57 | $res = Validator::validateOptional($data, array_merge(self::$required, self::$optional)); 58 | if ($res !== true) { 59 | if ($validators[self::V_SCHEME] === true) { 60 | throw new ValidationException('Not allowed keys "' . (implode(', ', $res)) . '""'); 61 | } 62 | } 63 | 64 | // CREATE RECURRENT PAYMENT ######################## 65 | 66 | $recurringPayment = new RecurringPayment(); 67 | 68 | // ### COMMON 69 | $recurringPayment->setAmount($data['amount']); 70 | $recurringPayment->setOrderNumber($data['order_number']); 71 | $recurringPayment->setOrderDescription($data['order_description']); 72 | 73 | // ### ITEMS 74 | foreach ($data['items'] as $param) { 75 | /** @phpstan-ignore-next-line */ 76 | if (!isset($param['name']) || !$param['name']) { 77 | if ($validators[self::V_SCHEME] === true) { 78 | throw new ValidationException('Item\'s name can\'t be empty or null.'); 79 | } 80 | } 81 | 82 | $item = new Item(); 83 | self::map($item, [ 84 | 'name' => 'name', 85 | 'amount' => 'amount', 86 | 'count' => 'count', 87 | 'vat_rate' => 'vatRate', 88 | 'type' => 'type', 89 | ], $param); 90 | $recurringPayment->addItem($item); 91 | } 92 | 93 | // ### ADDITIONAL PARAMETERS 94 | if (isset($data['additional_params'])) { 95 | foreach ($data['additional_params'] as $param) { 96 | $parameter = new Parameter(); 97 | self::map($parameter, ['name' => 'name', 'value' => 'value'], $param); 98 | $recurringPayment->addParameter($parameter); 99 | } 100 | } 101 | 102 | // VALIDATION PRICE & ITEMS PRICE ######## 103 | $itemsPrice = new Money(0, new Currency($recurringPayment->getCurrency())); 104 | $orderPrice = $recurringPayment->getAmount(); 105 | foreach ($recurringPayment->getItems() as $item) { 106 | $itemsPrice = $itemsPrice->add($item->getAmount()); 107 | } 108 | 109 | if (!$itemsPrice->equals($orderPrice)) { 110 | if ($validators[self::V_PRICES] === true) { 111 | throw new ValidationException(sprintf('Payment price (%s) and items price (%s) do not match', $orderPrice->getAmount(), $itemsPrice->getAmount())); 112 | } 113 | } 114 | 115 | return $recurringPayment; 116 | } 117 | 118 | /** 119 | * @param mixed[] $mapping 120 | * @param mixed[] $data 121 | */ 122 | public static function map(object $obj, array $mapping, array $data): object 123 | { 124 | foreach ($mapping as $from => $to) { 125 | if (isset($data[$from])) { 126 | $obj->{$to} = $data[$from]; 127 | } 128 | } 129 | 130 | return $obj; 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | */ 37 | private static $services = [ 38 | 'authentication' => null, 39 | 'accounts' => null, 40 | 'payments' => null, 41 | ]; 42 | 43 | public function __construct(Config $config) 44 | { 45 | $this->config = $config; 46 | } 47 | 48 | protected function getAuth(): Auth 49 | { 50 | if ($this->auth === null) { 51 | $this->auth = new Oauth2Client($this, $this->getHttp()); 52 | } 53 | 54 | return $this->auth; 55 | } 56 | 57 | public function setAuth(Auth $auth): void 58 | { 59 | $this->auth = $auth; 60 | } 61 | 62 | protected function getHttp(): Http 63 | { 64 | if ($this->http === null) { 65 | $this->http = new HttpClient(); 66 | } 67 | 68 | return $this->http; 69 | } 70 | 71 | public function setHttp(Http $http): void 72 | { 73 | $this->http = $http; 74 | } 75 | 76 | public function getGoId(): string 77 | { 78 | return $this->config->getGoId(); 79 | } 80 | 81 | public function getClientId(): string 82 | { 83 | return $this->config->getClientId(); 84 | } 85 | 86 | public function getClientSecret(): string 87 | { 88 | return $this->config->getClientSecret(); 89 | } 90 | 91 | public function getToken(): ?Token 92 | { 93 | return $this->token; 94 | } 95 | 96 | public function hasToken(): bool 97 | { 98 | return $this->token !== null; 99 | } 100 | 101 | /** 102 | * @param string|Token $token 103 | */ 104 | public function setToken($token): void 105 | { 106 | if (is_string($token)) { 107 | $this->token = new Token(); 108 | $this->token->accessToken = $token; 109 | } else { 110 | $this->token = $token; 111 | } 112 | } 113 | 114 | /** 115 | * API ********************************************************************* 116 | */ 117 | 118 | /** 119 | * @param mixed[] $credentials 120 | */ 121 | public function authenticate(array $credentials): string 122 | { 123 | if ($this->token === null) { 124 | $response = $this->getAuth()->authenticate($credentials); 125 | $this->token = Token::create((array) $response->getData()); 126 | } 127 | 128 | return $this->token->accessToken; 129 | } 130 | 131 | public function call(Request $request): Response 132 | { 133 | if ($this->token === null) { 134 | throw new GopayException('Invalid token. Please do authorization.'); 135 | } 136 | 137 | return $this->getHttp()->doRequest($request); 138 | } 139 | 140 | public function createPaymentsService(): PaymentsService 141 | { 142 | return new PaymentsService($this); 143 | } 144 | 145 | public function createAccountsService(): AccountsService 146 | { 147 | return new AccountsService($this); 148 | } 149 | 150 | public function createAuthenticationService(): AuthenticationService 151 | { 152 | return new AuthenticationService($this); 153 | } 154 | 155 | /** 156 | * MAGIC ******************************************************************* 157 | */ 158 | 159 | /** 160 | * @return mixed 161 | */ 162 | public function __get(string $name) 163 | { 164 | if (array_key_exists($name, self::$services)) { 165 | if (self::$services[$name] === null) { 166 | self::$services[$name] = call_user_func_array([$this, 'create' . ucfirst($name) . 'Service'], [$this]); 167 | } 168 | 169 | return self::$services[$name]; 170 | } 171 | 172 | return null; 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/Api/Entity/Payment.php: -------------------------------------------------------------------------------- 1 | payer; 55 | } 56 | 57 | public function setPayer(Payer $payer): void 58 | { 59 | $this->payer = $payer; 60 | } 61 | 62 | public function getTarget(): Target 63 | { 64 | return $this->target; 65 | } 66 | 67 | public function hasTarget(): bool 68 | { 69 | return $this->target !== null; 70 | } 71 | 72 | public function setTarget(Target $target): void 73 | { 74 | $this->target = $target; 75 | } 76 | 77 | public function getAmount(): Money 78 | { 79 | return $this->amount; 80 | } 81 | 82 | public function getAmountInCents(): string 83 | { 84 | return $this->amount->getAmount(); 85 | } 86 | 87 | public function setAmount(Money $amount): void 88 | { 89 | $this->amount = $amount; 90 | } 91 | 92 | /** 93 | * @return non-empty-string 94 | */ 95 | public function getCurrency(): string 96 | { 97 | /** @var string $code */ 98 | $code = $this->amount->getCurrency()->getCode(); 99 | 100 | if ($code === '') { 101 | throw new InvalidStateException('Currency code cannot be empty'); 102 | } 103 | 104 | return $code; 105 | } 106 | 107 | public function getOrderNumber(): ?string 108 | { 109 | return $this->orderNumber; 110 | } 111 | 112 | public function setOrderNumber(string $orderNumber): void 113 | { 114 | $this->orderNumber = $orderNumber; 115 | } 116 | 117 | public function getOrderDescription(): ?string 118 | { 119 | return $this->orderDescription; 120 | } 121 | 122 | public function setOrderDescription(string $description): void 123 | { 124 | $this->orderDescription = $description; 125 | } 126 | 127 | /** 128 | * @return Item[] 129 | */ 130 | public function getItems(): array 131 | { 132 | return $this->items; 133 | } 134 | 135 | /** 136 | * @param Item[] $items 137 | */ 138 | public function setItems(array $items): void 139 | { 140 | $this->items = $items; 141 | } 142 | 143 | public function addItem(Item $item): void 144 | { 145 | $this->items[] = $item; 146 | } 147 | 148 | public function getReturnUrl(): ?string 149 | { 150 | return $this->returnUrl; 151 | } 152 | 153 | public function setReturnUrl(string $url): void 154 | { 155 | $this->returnUrl = $url; 156 | } 157 | 158 | public function getNotifyUrl(): ?string 159 | { 160 | return $this->notifyUrl; 161 | } 162 | 163 | public function setNotifyUrl(string $url): void 164 | { 165 | $this->notifyUrl = $url; 166 | } 167 | 168 | /** 169 | * @return Parameter[] 170 | */ 171 | public function getParameters(): array 172 | { 173 | return $this->parameters; 174 | } 175 | 176 | /** 177 | * @param Parameter[] $parameters 178 | */ 179 | public function setParameters(array $parameters): void 180 | { 181 | $this->parameters = $parameters; 182 | } 183 | 184 | public function addParameter(Parameter $parameter): void 185 | { 186 | $this->parameters[] = $parameter; 187 | } 188 | 189 | public function getLang(): ?string 190 | { 191 | return $this->lang; 192 | } 193 | 194 | public function setLang(string $lang): void 195 | { 196 | $this->lang = $lang; 197 | } 198 | 199 | public function getEet(): ?Eet 200 | { 201 | return $this->eet; 202 | } 203 | 204 | public function setEet(Eet $eet): void 205 | { 206 | $this->eet = $eet; 207 | } 208 | 209 | public function isPreauthorization(): bool 210 | { 211 | return $this->preauthorization; 212 | } 213 | 214 | public function setPreauthorization(bool $preauth): void 215 | { 216 | $this->preauthorization = boolval($preauth); 217 | } 218 | 219 | /** 220 | * HELPERS ***************************************************************** 221 | */ 222 | 223 | /** 224 | * @param Item[] $items 225 | * @return mixed[] 226 | */ 227 | protected function formatItems(array $items): array 228 | { 229 | // Format items 230 | return array_map(function (Item $item) { 231 | return $item->toArray(); 232 | }, $items); 233 | } 234 | 235 | /** 236 | * @param Parameter[] $parameters 237 | * @return mixed[] 238 | */ 239 | protected function formatParameters(array $parameters): array 240 | { 241 | return array_map(function (Parameter $param) { 242 | return $param->toArray(); 243 | }, $parameters); 244 | } 245 | 246 | /** 247 | * ABSTRACT **************************************************************** 248 | */ 249 | 250 | /** 251 | * @return mixed[] 252 | */ 253 | public function toArray(): array 254 | { 255 | $data = []; 256 | 257 | $data['target'] = $this->target->toArray(); 258 | 259 | $data['amount'] = $this->getAmountInCents(); 260 | $data['currency'] = $this->getCurrency(); 261 | 262 | $data['order_number'] = $this->getOrderNumber(); 263 | $data['order_description'] = $this->getOrderDescription(); 264 | 265 | $data['items'] = $this->formatItems($this->getItems()); 266 | 267 | $data['callback'] = []; 268 | $data['callback']['return_url'] = $this->getReturnUrl(); 269 | $data['callback']['notification_url'] = $this->getNotifyUrl(); 270 | 271 | // NOT REQUIRED ==================================== 272 | 273 | $payer = $this->getPayer(); 274 | if ($payer !== null) { 275 | $data['payer'] = $payer->toArray(); 276 | } 277 | 278 | $parameters = $this->getParameters(); 279 | if (count($parameters) > 0) { 280 | $data['additional_params'] = $this->formatParameters($parameters); 281 | } 282 | 283 | $lang = $this->getLang(); 284 | if ($lang !== null) { 285 | $data['lang'] = $lang; 286 | } 287 | 288 | $eet = $this->getEet(); 289 | if ($eet !== null) { 290 | $data['eet'] = $eet->toArray(); 291 | } 292 | 293 | $preauth = $this->isPreauthorization(); 294 | if ($preauth) { 295 | $data['preauthorization'] = $preauth; 296 | } 297 | 298 | return $data; 299 | } 300 | 301 | } 302 | -------------------------------------------------------------------------------- /src/Api/Entity/PaymentFactory.php: -------------------------------------------------------------------------------- 1 | */ 49 | public static $validators = [ 50 | self::V_SCHEME => true, 51 | self::V_PRICES => true, 52 | ]; 53 | 54 | /** 55 | * @param mixed[] $data 56 | * @param mixed[] $validators 57 | */ 58 | public static function create(array $data, array $validators = []): Payment 59 | { 60 | $validators += self::$validators; 61 | 62 | // CHECK REQUIRED DATA ################### 63 | 64 | $res = Validator::validateRequired($data, self::$required); 65 | if ($res !== true) { 66 | throw new ValidationException('Missing keys "' . (implode(', ', $res)) . '"'); 67 | } 68 | 69 | $res = Validator::validateRequired($data['callback'], self::$requiredCallback); 70 | if ($res !== true) { 71 | throw new ValidationException('Missing keys "' . (implode(', ', $res)) . '" in callback definition'); 72 | } 73 | 74 | // CHECK SCHEME DATA ##################### 75 | 76 | $res = Validator::validateOptional($data, array_merge(self::$required, self::$optional)); 77 | if ($res !== true) { 78 | if ($validators[self::V_SCHEME] === true) { 79 | throw new ValidationException('Not allowed keys "' . (implode(', ', $res)) . '""'); 80 | } 81 | } 82 | 83 | // CREATE PAYMENT ######################## 84 | 85 | $payment = new Payment(); 86 | 87 | // ### PAYER 88 | if (isset($data['payer'])) { 89 | $payer = new Payer(); 90 | self::map($payer, [ 91 | 'allowed_payment_instruments' => 'allowedPaymentInstruments', 92 | 'default_payment_instrument' => 'defaultPaymentInstrument', 93 | 'allowed_swifts' => 'allowedSwifts', 94 | 'default_swift' => 'defaultSwift', 95 | ], $data['payer']); 96 | $payment->setPayer($payer); 97 | 98 | if (isset($data['payer']['contact'])) { 99 | $contact = new Contact(); 100 | self::map($contact, [ 101 | 'first_name' => 'firstname', 102 | 'last_name' => 'lastname', 103 | 'email' => 'email', 104 | 'phone_number' => 'phone', 105 | 'city' => 'city', 106 | 'street' => 'street', 107 | 'postal_code' => 'zip', 108 | 'country_code' => 'country', 109 | ], $data['payer']['contact']); 110 | $payer->contact = $contact; 111 | } 112 | } 113 | 114 | // ### TARGET 115 | if (isset($data['target'])) { 116 | $target = new Target(); 117 | self::map($target, ['type' => 'type', 'goid' => 'goid'], $data['target']); 118 | $payment->setTarget($target); 119 | } 120 | 121 | // ### COMMON 122 | $payment->setAmount($data['amount']); 123 | $payment->setOrderNumber($data['order_number']); 124 | if (array_key_exists('order_description', $data)) { 125 | $payment->setOrderDescription($data['order_description']); 126 | } 127 | 128 | $payment->setReturnUrl($data['callback']['return_url']); 129 | $payment->setNotifyUrl($data['callback']['notify_url']); 130 | 131 | // ### ITEMS 132 | foreach ($data['items'] as $param) { 133 | if (!isset($param['name']) || !$param['name']) { 134 | if ($validators[self::V_SCHEME] === true) { 135 | throw new ValidationException('Item\'s name can\'t be empty or null.'); 136 | } 137 | } 138 | 139 | $item = new Item(); 140 | self::map($item, [ 141 | 'name' => 'name', 142 | 'amount' => 'amount', 143 | 'count' => 'count', 144 | 'vat_rate' => 'vatRate', 145 | 'type' => 'type', 146 | ], $param); 147 | $payment->addItem($item); 148 | } 149 | 150 | // ### ADDITIONAL PARAMETERS 151 | if (isset($data['additional_params'])) { 152 | foreach ($data['additional_params'] as $param) { 153 | $parameter = new Parameter(); 154 | self::map($parameter, ['name' => 'name', 'value' => 'value'], $param); 155 | $payment->addParameter($parameter); 156 | } 157 | } 158 | 159 | // ### LANG 160 | if (isset($data['lang'])) { 161 | $payment->setLang($data['lang']); 162 | } 163 | 164 | // VALIDATION PRICE & ITEMS PRICE ######## 165 | $itemsPrice = new Money(0, new Currency($payment->getCurrency())); 166 | 167 | $orderPrice = $payment->getAmount(); 168 | foreach ($payment->getItems() as $item) { 169 | $itemsPrice = $itemsPrice->add($item->getAmount()); 170 | } 171 | 172 | if (!$itemsPrice->equals($orderPrice)) { 173 | if ($validators[self::V_PRICES] === true) { 174 | throw new ValidationException(sprintf('Payment price (%s) and items price (%s) do not match', $orderPrice->getAmount(), $itemsPrice->getAmount())); 175 | } 176 | } 177 | 178 | // ### EET 179 | if (isset($data['eet'])) { 180 | $eet = new Eet(); 181 | self::map($eet, [ 182 | 'mena' => 'currency', 183 | 'celk_trzba' => 'sum', 184 | 'zakl_dan1' => 'taxBase', 185 | 'zakl_nepodl_dph' => 'taxBaseNoVat', 186 | 'dan1' => 'tax', 187 | 'zakl_dan2' => 'taxBaseReducedRateFirst', 188 | 'dan2' => 'taxReducedRateFirst', 189 | 'zakl_dan3' => 'taxBaseReducedRateSecond', 190 | 'dan3' => 'taxReducedRateSecond', 191 | 'urceno_cerp_zuct' => 'subsequentDrawing', 192 | 'cerp_zuct' => 'subsequentlyDrawn', 193 | ], $data['eet']); 194 | 195 | $eetSum = $eet->getSum(); 196 | $eetTotal = $eet->getTotal(); 197 | 198 | if ($validators[self::V_PRICES] === true) { 199 | if (!$eetSum->equals($eetTotal)) { 200 | throw new ValidationException(sprintf('EET sum (%s) and EET tax sum (%s) do not match', $eetSum->getAmount(), $eetTotal->getAmount())); 201 | } 202 | 203 | if (!$eetSum->equals($orderPrice)) { 204 | throw new ValidationException(sprintf('EET sum (%s) and order sum (%s) do not match', $eetSum->getAmount(), $orderPrice->getAmount())); 205 | } 206 | } 207 | 208 | $payment->setEet($eet); 209 | } 210 | 211 | // ### PREAUTHORIZATION 212 | if (isset($data['preauthorization'])) { 213 | $payment->setPreauthorization($data['preauthorization']); 214 | } 215 | 216 | return $payment; 217 | } 218 | 219 | /** 220 | * @param mixed[] $mapping 221 | * @param mixed[] $data 222 | */ 223 | public static function map(object $obj, array $mapping, array $data): object 224 | { 225 | foreach ($mapping as $from => $to) { 226 | if (isset($data[$from])) { 227 | $obj->{$to} = $data[$from]; 228 | } 229 | } 230 | 231 | return $obj; 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /src/Api/Objects/Eet.php: -------------------------------------------------------------------------------- 1 | sum; 48 | } 49 | 50 | public function getSumInCents(): string 51 | { 52 | return $this->getSum()->getAmount(); 53 | } 54 | 55 | public function getTaxBase(): ?Money 56 | { 57 | return $this->taxBase; 58 | } 59 | 60 | public function getTaxBaseInCents(): ?string 61 | { 62 | return $this->taxBase !== null ? $this->taxBase->getAmount() : null; 63 | } 64 | 65 | public function getTax(): ?Money 66 | { 67 | return $this->tax; 68 | } 69 | 70 | public function getTaxInCents(): ?string 71 | { 72 | return $this->tax !== null ? $this->tax->getAmount() : null; 73 | } 74 | 75 | public function getTaxBaseReducedRateFirst(): ?Money 76 | { 77 | return $this->taxBaseReducedRateFirst; 78 | } 79 | 80 | public function getTaxBaseReducedRateFirstInCents(): ?string 81 | { 82 | return $this->taxBaseReducedRateFirst !== null ? $this->taxBaseReducedRateFirst->getAmount() : null; 83 | } 84 | 85 | public function getTaxReducedRateFirst(): ?Money 86 | { 87 | return $this->taxReducedRateFirst; 88 | } 89 | 90 | public function getTaxReducedRateFirstInCents(): ?string 91 | { 92 | return $this->taxReducedRateFirst !== null ? $this->taxReducedRateFirst->getAmount() : null; 93 | } 94 | 95 | public function getTaxBaseReducedRateSecond(): ?Money 96 | { 97 | return $this->taxBaseReducedRateSecond; 98 | } 99 | 100 | public function getTaxBaseReducedRateSecondInCents(): ?string 101 | { 102 | return $this->taxBaseReducedRateSecond !== null ? $this->taxBaseReducedRateSecond->getAmount() : null; 103 | } 104 | 105 | public function getTaxReducedRateSecond(): ?Money 106 | { 107 | return $this->taxReducedRateSecond; 108 | } 109 | 110 | public function getTaxReducedRateSecondInCents(): ?string 111 | { 112 | return $this->taxReducedRateSecond !== null ? $this->taxReducedRateSecond->getAmount() : null; 113 | } 114 | 115 | /** 116 | * @return non-empty-string 117 | */ 118 | public function getCurrency(): string 119 | { 120 | /** @var string $code */ 121 | $code = $this->sum->getCurrency()->getCode(); 122 | 123 | if ($code === '') { 124 | throw new InvalidStateException('Currency code cannot be empty'); 125 | } 126 | 127 | return $code; 128 | } 129 | 130 | public function getTaxBaseNoVat(): ?Money 131 | { 132 | return $this->taxBaseNoVat; 133 | } 134 | 135 | public function getSubsequentDrawing(): ?Money 136 | { 137 | return $this->subsequentDrawing; 138 | } 139 | 140 | public function getSubsequentDrawingInCents(): ?string 141 | { 142 | return $this->subsequentDrawing !== null ? $this->subsequentDrawing->getAmount() : null; 143 | } 144 | 145 | public function getSubsequentlyDrawn(): ?Money 146 | { 147 | return $this->subsequentlyDrawn; 148 | } 149 | 150 | public function getSubsequentlyDrawnInCents(): ?string 151 | { 152 | return $this->subsequentlyDrawn !== null ? $this->subsequentlyDrawn->getAmount() : null; 153 | } 154 | 155 | public function setSum(Money $sum): void 156 | { 157 | $this->sum = $sum; 158 | } 159 | 160 | public function setTaxBase(Money $taxBase): void 161 | { 162 | $this->taxBase = $taxBase; 163 | } 164 | 165 | public function setTax(Money $tax): void 166 | { 167 | $this->tax = $tax; 168 | } 169 | 170 | public function setTaxBaseReducedRateFirst(Money $taxBaseReducedRateFirst): void 171 | { 172 | $this->taxBaseReducedRateFirst = $taxBaseReducedRateFirst; 173 | } 174 | 175 | public function setTaxReducedRateFirst(Money $taxReducedRateFirst): void 176 | { 177 | $this->taxReducedRateFirst = $taxReducedRateFirst; 178 | } 179 | 180 | public function setTaxBaseReducedRateSecond(Money $taxBaseReducedRateSecond): void 181 | { 182 | $this->taxBaseReducedRateSecond = $taxBaseReducedRateSecond; 183 | } 184 | 185 | public function setTaxReducedRateSecond(Money $taxReducedRateSecond): void 186 | { 187 | $this->taxReducedRateSecond = $taxReducedRateSecond; 188 | } 189 | 190 | public function setTaxBaseNoVat(Money $taxBaseNoVat): void 191 | { 192 | $this->taxBaseNoVat = $taxBaseNoVat; 193 | } 194 | 195 | public function getTotal(): Money 196 | { 197 | $total = new Money(0, new Currency($this->getCurrency())); 198 | 199 | if ($this->tax !== null) { 200 | $total = $total->add($this->tax); 201 | } 202 | 203 | if ($this->taxBaseNoVat !== null) { 204 | $total = $total->add($this->taxBaseNoVat); 205 | } 206 | 207 | if ($this->taxBase !== null) { 208 | $total = $total->add($this->taxBase); 209 | } 210 | 211 | if ($this->taxBaseReducedRateFirst !== null) { 212 | $total = $total->add($this->taxBaseReducedRateFirst); 213 | } 214 | 215 | if ($this->taxReducedRateFirst !== null) { 216 | $total = $total->add($this->taxReducedRateFirst); 217 | } 218 | 219 | if ($this->taxBaseReducedRateSecond !== null) { 220 | $total = $total->add($this->taxBaseReducedRateSecond); 221 | } 222 | 223 | if ($this->taxReducedRateSecond !== null) { 224 | $total = $total->add($this->taxReducedRateSecond); 225 | } 226 | 227 | if ($this->subsequentDrawing !== null) { 228 | $total = $total->add($this->subsequentDrawing); 229 | } 230 | 231 | if ($this->subsequentlyDrawn !== null) { 232 | $total = $total->add($this->subsequentlyDrawn); 233 | } 234 | 235 | return $total; 236 | } 237 | 238 | /** 239 | * @return mixed[] 240 | */ 241 | public function toArray(): array 242 | { 243 | $data = []; 244 | $data['celk_trzba'] = $this->getSumInCents(); 245 | $data['mena'] = $this->getCurrency(); 246 | 247 | if ($this->getTaxBaseNoVat() !== null) { 248 | $data['zakl_nepodl_dph'] = $this->getTaxBaseNoVat(); 249 | } 250 | 251 | if ($this->getTaxBase() !== null && $this->getTax() !== null) { 252 | $data['zakl_dan1'] = $this->getTaxBaseInCents(); 253 | $data['dan1'] = $this->getTaxInCents(); 254 | } 255 | 256 | if ($this->getTaxBaseReducedRateFirst() !== null && $this->getTaxReducedRateFirst() !== null) { 257 | $data['zakl_dan2'] = $this->getTaxBaseReducedRateFirstInCents(); 258 | $data['dan2'] = $this->getTaxReducedRateFirstInCents(); 259 | } 260 | 261 | if ($this->getTaxBaseReducedRateSecond() !== null && $this->getTaxReducedRateSecond() !== null) { 262 | $data['zakl_dan3'] = $this->getTaxBaseReducedRateSecondInCents(); 263 | $data['dan3'] = $this->getTaxReducedRateSecondInCents(); 264 | } 265 | 266 | if ($this->getSubsequentDrawing() !== null) { 267 | $data['urceno_cerp_zuct'] = $this->getSubsequentDrawingInCents(); 268 | } 269 | 270 | if ($this->getSubsequentlyDrawn() !== null) { 271 | $data['cerp_zuct'] = $this->getSubsequentlyDrawnInCents(); 272 | } 273 | 274 | return $data; 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /src/Api/Entity/RecurrentPaymentFactory.php: -------------------------------------------------------------------------------- 1 | */ 51 | public static $validators = [ 52 | self::V_SCHEME => true, 53 | self::V_PRICES => true, 54 | ]; 55 | 56 | /** 57 | * @param mixed $data 58 | * @param mixed[] $validators 59 | */ 60 | public static function create($data, array $validators = []): RecurrentPayment 61 | { 62 | // Convert to array 63 | $data = (array) $data; 64 | $validators += self::$validators; 65 | 66 | // CHECK REQUIRED DATA ################### 67 | 68 | $res = Validator::validateRequired($data, self::$required); 69 | if ($res !== true) { 70 | throw new ValidationException('Missing keys "' . (implode(', ', $res)) . '""'); 71 | } 72 | 73 | $res = Validator::validateRequired($data['callback'], self::$requiredCallback); 74 | if ($res !== true) { 75 | throw new ValidationException('Missing keys "' . (implode(', ', $res)) . '" in callback definition'); 76 | } 77 | 78 | // CHECK SCHEME DATA ##################### 79 | 80 | $res = Validator::validateOptional($data, array_merge(self::$required, self::$optional)); 81 | if ($res !== true) { 82 | if ($validators[self::V_SCHEME] === true) { 83 | throw new ValidationException('Not allowed keys "' . (implode(', ', $res)) . '""'); 84 | } 85 | } 86 | 87 | // CREATE RECURRENT PAYMENT ######################## 88 | 89 | $recurrentPayment = new RecurrentPayment(); 90 | 91 | // ### PAYER 92 | if (isset($data['payer'])) { 93 | $payer = new Payer(); 94 | self::map($payer, [ 95 | 'allowed_payment_instruments' => 'allowedPaymentInstruments', 96 | 'default_payment_instrument' => 'defaultPaymentInstrument', 97 | 'allowed_swifts' => 'allowedSwifts', 98 | 'default_swift' => 'defaultSwift', 99 | ], $data['payer']); 100 | $recurrentPayment->setPayer($payer); 101 | 102 | if (isset($data['payer']['contact'])) { 103 | $contact = new Contact(); 104 | self::map($contact, [ 105 | 'first_name' => 'firstname', 106 | 'last_name' => 'lastname', 107 | 'email' => 'email', 108 | 'phone_number' => 'phone', 109 | 'city' => 'city', 110 | 'street' => 'street', 111 | 'postal_code' => 'zip', 112 | 'country_code' => 'country', 113 | ], $data['payer']['contact']); 114 | $payer->contact = $contact; 115 | } 116 | } 117 | 118 | // ### TARGET 119 | if (isset($data['target'])) { 120 | $target = new Target(); 121 | self::map($target, ['type' => 'type', 'goid' => 'goid'], $data['target']); 122 | $recurrentPayment->setTarget($target); 123 | } 124 | 125 | // ### COMMON 126 | $recurrentPayment->setAmount($data['amount']); 127 | $recurrentPayment->setOrderNumber($data['order_number']); 128 | $recurrentPayment->setOrderDescription($data['order_description']); 129 | $recurrentPayment->setReturnUrl($data['callback']['return_url']); 130 | $recurrentPayment->setNotifyUrl($data['callback']['notify_url']); 131 | 132 | // ### ITEMS 133 | foreach ($data['items'] as $param) { 134 | /** @phpstan-ignore-next-line */ 135 | if (!isset($param['name']) || !$param['name']) { 136 | if ($validators[self::V_SCHEME] === true) { 137 | throw new ValidationException('Item\'s name can\'t be empty or null.'); 138 | } 139 | } 140 | 141 | $item = new Item(); 142 | self::map($item, [ 143 | 'name' => 'name', 144 | 'amount' => 'amount', 145 | 'count' => 'count', 146 | 'vat_rate' => 'vatRate', 147 | 'type' => 'type', 148 | ], $param); 149 | $recurrentPayment->addItem($item); 150 | } 151 | 152 | // ### RECURRENCE 153 | if (isset($data['recurrence'])) { 154 | $recurrence = new Recurrence(); 155 | self::map($recurrence, ['recurrence_cycle' => 'cycle', 'recurrence_period' => 'period', 'recurrence_date_to' => 'dateTo'], $data['recurrence']); 156 | $recurrentPayment->setRecurrence($recurrence); 157 | } 158 | 159 | // ### ADDITIONAL PARAMETERS 160 | if (isset($data['additional_params'])) { 161 | foreach ($data['additional_params'] as $param) { 162 | $parameter = new Parameter(); 163 | self::map($parameter, ['name' => 'name', 'value' => 'value'], $param); 164 | $recurrentPayment->addParameter($parameter); 165 | } 166 | } 167 | 168 | // ### LANG 169 | if (isset($data['lang'])) { 170 | $recurrentPayment->setLang($data['lang']); 171 | } 172 | 173 | // VALIDATION PRICE & ITEMS PRICE ######## 174 | $itemsPrice = new Money(0, new Currency($recurrentPayment->getCurrency())); 175 | 176 | $orderPrice = $recurrentPayment->getAmount(); 177 | foreach ($recurrentPayment->getItems() as $item) { 178 | $itemsPrice = $itemsPrice->add($item->getAmount()); 179 | } 180 | 181 | if (!$itemsPrice->equals($orderPrice)) { 182 | if ($validators[self::V_PRICES] === true) { 183 | throw new ValidationException(sprintf('Payment price (%s) and items price (%s) do not match', $orderPrice->getAmount(), $itemsPrice->getAmount())); 184 | } 185 | } 186 | 187 | // ### EET 188 | if (isset($data['eet'])) { 189 | $eet = new Eet(); 190 | self::map($eet, [ 191 | 'celk_trzba' => 'sum', 192 | 'zakl_dan1' => 'taxBase', 193 | 'zakl_nepodl_dph' => 'taxBaseNoVat', 194 | 'dan1' => 'tax', 195 | 'zakl_dan2' => 'taxBaseReducedRateFirst', 196 | 'dan2' => 'taxReducedRateFirst', 197 | 'zakl_dan3' => 'taxBaseReducedRateSecond', 198 | 'dan3' => 'taxReducedRateSecond', 199 | ], $data['eet']); 200 | 201 | $eetSum = $eet->getSum(); 202 | $eetTotal = $eet->getTotal(); 203 | 204 | if ($validators[self::V_PRICES] === true) { 205 | if (!$eetSum->equals($eetTotal)) { 206 | throw new ValidationException(sprintf('EET sum (%s) and EET tax sum (%s) do not match', $eetSum->getAmount(), $eetTotal->getAmount())); 207 | } 208 | 209 | if (!$eetSum->equals($orderPrice)) { 210 | throw new ValidationException(sprintf('EET sum (%s) and order sum (%s) do not match', $eetSum->getAmount(), $orderPrice->getAmount())); 211 | } 212 | } 213 | 214 | $recurrentPayment->setEet($eet); 215 | } 216 | 217 | return $recurrentPayment; 218 | } 219 | 220 | /** 221 | * @param mixed[] $mapping 222 | * @param mixed[] $data 223 | */ 224 | public static function map(object $obj, array $mapping, array $data): object 225 | { 226 | foreach ($mapping as $from => $to) { 227 | if (isset($data[$from])) { 228 | $obj->{$to} = $data[$from]; 229 | } 230 | } 231 | 232 | return $obj; 233 | } 234 | 235 | } 236 | --------------------------------------------------------------------------------