├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── AccessToken.php ├── Entities │ ├── AbstractEntity.php │ ├── BankAccount.php │ ├── Payment.php │ ├── PaymentRequest.php │ ├── Platform.php │ └── User.php ├── Environment.php ├── Exceptions │ ├── AccessTokenException.php │ ├── PHPTikkieException.php │ ├── RequestException.php │ └── ResponseException.php ├── PHPTikkie.php ├── Requests │ ├── AbstractRequest.php │ ├── CreatePaymentRequestRequest.php │ ├── CreatePlatformRequest.php │ ├── CreateUserRequest.php │ ├── FetchPaymentRequestRequest.php │ ├── FetchPaymentRequestsRequest.php │ ├── FetchPlatformsRequest.php │ └── FetchUsersRequest.php └── Response.php └── tests ├── CreatePaymentRequestTest.php ├── CreatePlatformTest.php ├── CreateUserTest.php ├── EnvironmentTest.php ├── FetchPaymentRequestTest.php ├── FetchPaymentRequestsTest.php ├── FetchPlatformsTest.php ├── FetchUsersTest.php ├── Mock ├── AccessToken.txt ├── CreatePaymentRequestSuccess.txt ├── CreatePlatformSuccess.txt ├── CreateUserSuccess.txt ├── FetchPaymentRequestSuccess.txt ├── FetchPaymentRequestsSuccess.txt ├── FetchPlatformsSuccess.txt ├── FetchUsersSuccess.txt ├── InvalidApiKey.txt ├── ValidationError.txt ├── private_rsa.pem └── public_rsa.pem └── TestCase.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.1 5 | - 7.2 6 | - 7.3 7 | 8 | ## Cache composer 9 | cache: 10 | directories: 11 | - $HOME/.composer/cache 12 | 13 | install: 14 | - travis_retry composer install --prefer-dist --no-interaction 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.4] - 2019-11-15 10 | ### Fixed 11 | - `externalId` of payment requests is now documented as mandatory. ([#6](https://github.com/jarnovanleeuwen/php-tikkie/pull/6)) 12 | 13 | ### Deprecated 14 | - `Platform::USAGE_TYPE_OTHERS` is deprecated because it is no longer mentioned in the official documentation. 15 | 16 | ## [0.2.3] - 2019-04-24 17 | ### Fixed 18 | - Due to a bug at Tikkie, dates may be returned as a epoch timestamp with milliseconds instead of an ISO-8601 formatted string. This has been reported to Tikkie, and while we wait support for those timestamps has been added to the library. 19 | 20 | ## [0.2.2] - 2018-10-31 21 | ### Changed 22 | - All Guzzle-related exceptions are now turned into a `RequestException`. 23 | 24 | ## [0.2.1] - 2018-09-27 25 | ### Changed 26 | - Trace ID of requests is now included in exception messages. ([#3](https://github.com/jarnovanleeuwen/php-tikkie/pull/3)) 27 | 28 | ## [0.2.0] - 2018-06-20 29 | ### Added 30 | - Added a test suite. 31 | 32 | ### Changed 33 | - PHPTikkie now requires PHP 7.1+ 34 | 35 | ## [0.1.2] - 2018-06-19 36 | ### Fixed 37 | - Cast `from` and `to` dates to UTC time zone. ([#1](https://github.com/jarnovanleeuwen/php-tikkie/pull/1)) 38 | 39 | ## [0.1.1] - 2018-03-05 40 | ### Fixed 41 | - Check if responses are valid JSON. 42 | - Fix the class name of `ResponseException`. 43 | 44 | ## [0.1.0] - 2017-12-12 45 | ### Added 46 | - Initial implementation of the Tikkie API. 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jarno van Leeuwen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | 5 | [![Build Status](https://travis-ci.com/jarnovanleeuwen/php-tikkie.svg?branch=master)](https://travis-ci.com/jarnovanleeuwen/php-tikkie) 6 | 7 | ⚠️ Tikkie has announced that on **01-01-2021** it will stop supporting the Tikkie Payment Request API (the API that this library implements) in favor of the new Tikkie API. See https://medium.com/abn-amro-developer/abn-amro-introduces-the-new-tikkie-api-87c8bb54720a for more information, including a roadmap. 8 | 9 | Currently, I am not planning to update this library. This might change in the future, but meanwhile PRs are welcome. 10 | 11 | --- 12 | 13 | #### Easily create payment requests through [Tikkie](https://tikkie.me/). 14 | 15 | Unofficial PHP implementation of the [Tikkie Payment Request API](https://developer.abnamro.com/content/tikkie-payment-request) (deprecated, available until 01-01-2021). 16 | 17 | # Installation 18 | 19 | *PHPTikkie requires PHP 7.1+* 20 | 21 | Add this package to your project using [Composer](https://getcomposer.org/): 22 | 23 | `composer require jarnovanleeuwen/php-tikkie` 24 | 25 | # Usage 26 | 27 | ### Initialise PHPTikkie 28 | ```php 29 | use PHPTikkie\Environment; 30 | use PHPTikkie\PHPTikkie; 31 | 32 | $apiKey = "abc123"; 33 | $testMode = true; 34 | 35 | $environment = new Environment($apiKey, $testMode); 36 | $environment->loadPrivateKey('private_rsa.pem'); 37 | 38 | $tikkie = new PHPTikkie($environment); 39 | ``` 40 | 41 | ### Create platform 42 | ```php 43 | use PHPTikkie\Entities\Platform; 44 | 45 | $platform = $tikkie->newPlatform([ 46 | // Mandatory attributes 47 | 'name' => 'YourPlatform', 48 | 'phoneNumber' => '06123456789', 49 | 'platformUsage' => Platform::USAGE_TYPE_MYSELF, 50 | 51 | // Optional attributes 52 | 'email' => 'tikkie@yourcompany.com', 53 | 'notificationUrl' => '' 54 | ])->save(); 55 | 56 | $platformToken = $platform->platformToken; 57 | ``` 58 | 59 | ### Create user 60 | ```php 61 | $user = $tikkie->newUser($platformToken, [ 62 | 'name' => 'ExamplePlatform', 63 | 'phoneNumber' => '06123456789', 64 | 'iban' => 'NL00BANK123456789', 65 | 'bankAccountLabel' => 'YourLabel' 66 | ])->save(); 67 | 68 | $userToken = $user->userToken; 69 | $bankAccountToken = $user->bankAccounts[0]->bankAccountToken; 70 | ``` 71 | 72 | ### Create payment request 73 | ```php 74 | $paymentRequest = $tikkie->newPaymentRequest($platformToken, $userToken, $bankAccountToken, [ 75 | // Mandatory attributes 76 | 'amountInCents' => '1250', 77 | 'currency' => 'EUR', 78 | 'description' => 'Thank you', 79 | 'externalId' => 'Order 1234' 80 | ])->save(); 81 | 82 | $tikkieUrl = $paymentRequest->paymentRequestUrl; 83 | $paymentRequestToken = $paymentRequest->paymentRequestToken; 84 | ``` 85 | 86 | ### Get payment request 87 | ```php 88 | function paymentRequest(string $platformToken, string $userToken, string $paymentRequestToken): PaymentRequest 89 | ``` 90 | 91 | ### List platforms 92 | ```php 93 | function platforms(): Platform[] 94 | ``` 95 | 96 | ### List users 97 | ```php 98 | function users(string $platformToken): User[] 99 | ``` 100 | 101 | ### List payment requests 102 | ```php 103 | function paymentRequests(string $platformToken, string $userToken, int $offset, int $limit, DateTimeInterface $fromDate = null, DateTimeInterface $toDate = null): PaymentRequest[] 104 | ``` 105 | 106 | ### Process payments 107 | ```php 108 | $paymentRequest = $tikkie->paymentRequest($platformToken, $userToken, $paymentRequestToken); 109 | 110 | foreach ($paymentRequest->payments as $payment) { 111 | if ($payment->isPaid()) { 112 | // Payment successful 113 | } 114 | } 115 | ``` 116 | 117 | ### Exception handling 118 | All methods may return a `PHPTikkieException` containing an error code and description. 119 | 120 | ```php 121 | use PHPTikkie\Exceptions\PHPTikkieException; 122 | 123 | try { 124 | var_dump($tikkie->platforms()); 125 | } catch (PHPTikkieException $exception) { 126 | print $exception->getMessage(); // [ERR_2005_002] The API Key is invalid for the requested resource | traceId: 6fda2ce8-225d-4ca2-920a-b687c7aeb2f3 | (see https://developer.abnamro.com/get-started#obtaining-an-api-key) 127 | } 128 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jarnovanleeuwen/php-tikkie", 3 | "type": "library", 4 | "description": "Implementation of the Tikkie API interface.", 5 | "keywords": [ 6 | "tikkie", 7 | "payment", 8 | "pay", 9 | "api", 10 | "abnamro" 11 | ], 12 | "homepage": "https://github.com/jarnovanleeuwen/php-tikkie", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Jarno van Leeuwen", 17 | "email": "vanleeuwen.jarno@gmail.com" 18 | } 19 | ], 20 | "autoload": { 21 | "psr-4": { 22 | "PHPTikkie\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "PHPTikkie\\Tests\\": "tests/" 28 | } 29 | }, 30 | "require": { 31 | "php": ">=7.1.0", 32 | "guzzlehttp/guzzle": "~6.0", 33 | "firebase/php-jwt": "~5.0" 34 | }, 35 | "require-dev": { 36 | "phpunit/phpunit": "^6" 37 | }, 38 | "abandoned": true 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/AccessToken.php: -------------------------------------------------------------------------------- 1 | token = $token; 26 | $this->lifetime = $lifetime; 27 | $this->expiryTime = time() + $lifetime; 28 | } 29 | 30 | public function getLifetime(): int 31 | { 32 | return $this->lifetime; 33 | } 34 | 35 | public function getToken(): string 36 | { 37 | return $this->token; 38 | } 39 | 40 | public function isValid(): bool 41 | { 42 | return time() < $this->expiryTime; 43 | } 44 | 45 | public function __toString(): string 46 | { 47 | return $this->token; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Entities/AbstractEntity.php: -------------------------------------------------------------------------------- 1 | tikkie = $tikkie; 22 | } 23 | 24 | protected function getTikkie(): PHPTikkie 25 | { 26 | return $this->tikkie; 27 | } 28 | 29 | public function setAttributes(array $attributes) 30 | { 31 | foreach ($attributes as $key => $value) { 32 | if (in_array($key, $this->fillableAttributes)) { 33 | $this->{$key} = $value; 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * Convert an ISO-8601 formatted string to DateTimeImmutable. 40 | */ 41 | protected function toDateTime(string $representation): DateTimeImmutable 42 | { 43 | // Due to a Tikkie bug, the API may return epoch timestamps with milliseconds instead of a ISO-8601 formatted string. 44 | // I reported this on 24-04-2019. 45 | if (is_numeric($representation)) { 46 | // Remove milliseconds and prepend with @ to mark as timestamp. 47 | $representation = '@'.substr($representation, 0, 10); 48 | } 49 | 50 | return new DateTimeImmutable($representation); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Entities/BankAccount.php: -------------------------------------------------------------------------------- 1 | onlinePaymentStatus === static::STATUS_PAID; 56 | } 57 | 58 | public function setAttributes(array $attributes) 59 | { 60 | parent::setAttributes($attributes); 61 | 62 | if (isset($attributes['created'])) { 63 | $this->created = $this->toDateTime($attributes['created']); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Entities/PaymentRequest.php: -------------------------------------------------------------------------------- 1 | {$dateAttribute} = $this->toDateTime($attributes[$dateAttribute]); 97 | } 98 | } 99 | 100 | $this->payments = []; 101 | 102 | if ($payments = $attributes['payments'] ?? null) { 103 | foreach ($payments as $paymentData) { 104 | $payment = new Payment($this->getTikkie()); 105 | 106 | $payment->setAttributes($paymentData); 107 | 108 | $this->payments[] = $payment; 109 | } 110 | } 111 | } 112 | 113 | public function save(): self 114 | { 115 | $this->getTikkie()->persistPaymentRequest($this); 116 | 117 | return $this; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Entities/Platform.php: -------------------------------------------------------------------------------- 1 | status === static::STATUS_ACTIVE; 59 | } 60 | 61 | public function save(): self 62 | { 63 | $this->getTikkie()->persistPlatform($this); 64 | 65 | return $this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Entities/User.php: -------------------------------------------------------------------------------- 1 | status === static::STATUS_ACTIVE; 59 | } 60 | 61 | public function setAttributes(array $attributes) 62 | { 63 | parent::setAttributes($attributes); 64 | 65 | $this->bankAccounts = []; 66 | 67 | if ($bankAccounts = $attributes['bankAccounts'] ?? null) { 68 | foreach ($bankAccounts as $bankAccountData) { 69 | $bankAccount = new BankAccount($this->getTikkie()); 70 | 71 | $bankAccount->setAttributes($bankAccountData); 72 | 73 | $this->bankAccounts[] = $bankAccount; 74 | } 75 | } 76 | } 77 | 78 | public function save(): self 79 | { 80 | $this->getTikkie()->persistUser($this); 81 | 82 | return $this; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Environment.php: -------------------------------------------------------------------------------- 1 | apiKey = $apiKey; 54 | $this->testMode = $testMode; 55 | 56 | $this->httpClient = new HttpClient(array_merge([ 57 | 'base_uri' => $testMode ? static::SANDBOX_API_URL : static::PRODUCTION_API_URL, 58 | 'http_errors' => false, 59 | 'headers' => ['User-Agent' => 'PHPTikkie/'.static::VERSION] 60 | ], $requestOptions)); 61 | } 62 | 63 | public function loadPrivateKey(string $path, string $hashAlgorithm = self::DEFAULT_HASH_ALGORITHM) 64 | { 65 | return $this->loadPrivateKeyFromString(file_get_contents($path), $hashAlgorithm); 66 | } 67 | 68 | public function loadPrivateKeyFromString(string $privateKey, string $hashAlgorithm = self::DEFAULT_HASH_ALGORITHM) 69 | { 70 | $this->privateKey = $privateKey; 71 | $this->hashAlgorithm = $hashAlgorithm; 72 | } 73 | 74 | protected function getAccessToken(): AccessToken 75 | { 76 | if ($this->accessToken && $this->accessToken->isValid()) { 77 | return $this->accessToken; 78 | } 79 | 80 | return $this->accessToken = $this->requestAccessToken(); 81 | } 82 | 83 | protected function getJsonWebToken(): string 84 | { 85 | if (empty($this->privateKey)) { 86 | throw new AccessTokenException("Cannot create JSON Web Token because no Private Key has been set."); 87 | } 88 | 89 | $now = time(); 90 | 91 | return JWT::encode([ 92 | 'exp' => $now + 60, // Expires after one minute 93 | 'nbf' => $now - 60, 94 | 'iss' => 'PHPTikkie', 95 | 'sub' => $this->apiKey, 96 | 'aud' => $this->testMode ? static::SANDBOX_TOKEN_URL : static::PRODUCTION_TOKEN_URL 97 | ], $this->privateKey, $this->hashAlgorithm); 98 | } 99 | 100 | /** 101 | * @throws AccessTokenException 102 | */ 103 | protected function requestAccessToken(): AccessToken 104 | { 105 | try { 106 | $response = $this->httpClient->request('POST', '/v1/oauth/token', [ 107 | 'headers' => [ 108 | 'API-Key' => $this->apiKey 109 | ], 110 | 'form_params' => [ 111 | 'client_assertion' => $this->getJsonWebToken(), 112 | 'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 113 | 'grant_type' => 'client_credentials', 114 | 'scope' => 'tikkie' 115 | ] 116 | ]); 117 | 118 | if ($response->getStatusCode() == 200 && is_object($responseData = json_decode($response->getBody()))) { 119 | return new AccessToken($responseData->access_token, (int) $responseData->expires_in); 120 | } 121 | 122 | throw new AccessTokenException($response->getBody()); 123 | } catch (GuzzleException $exception) { 124 | throw new AccessTokenException($exception->getMessage()); 125 | } 126 | } 127 | 128 | protected function getRequestOptions(AbstractRequest $request): array 129 | { 130 | $options = [ 131 | RequestOptions::HEADERS => [ 132 | 'API-Key' => $this->apiKey, 133 | 'Authorization' => "Bearer {$this->getAccessToken()}", 134 | 'Accept' => 'application/json' 135 | ] 136 | ]; 137 | 138 | if ($parameters = $request->getParameters()) { 139 | $options[RequestOptions::QUERY] = $parameters; 140 | } 141 | 142 | if ($payload = $request->getPayload()) { 143 | $options[RequestOptions::JSON] = $payload; 144 | } 145 | 146 | return array_merge_recursive($options, $request->getRequestOptions()); 147 | } 148 | 149 | /** 150 | * @throws RequestException 151 | */ 152 | public function send(AbstractRequest $request): Response 153 | { 154 | try { 155 | $response = $this->httpClient->request( 156 | $request->getMethod(), 157 | $request->getUri(), 158 | $this->getRequestOptions($request) 159 | ); 160 | 161 | if (in_array($response->getStatusCode(), [200, 201])) { 162 | return new Response($response); 163 | } 164 | 165 | throw new RequestException($response->getBody()); 166 | } catch (GuzzleException $exception) { 167 | throw new RequestException($exception->getMessage()); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Exceptions/AccessTokenException.php: -------------------------------------------------------------------------------- 1 | errors = $response->errors ?? []; 21 | 22 | if (count($this->errors) > 0) { 23 | $error = $this->errors[0]; 24 | 25 | $message = "[{$error->code}] {$error->message} " 26 | . "| traceId: {$error->traceId} | " 27 | . "(see {$error->reference})"; 28 | } 29 | } 30 | 31 | parent::__construct($message, $code, $previous); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Exceptions/RequestException.php: -------------------------------------------------------------------------------- 1 | environment = $environment; 26 | } 27 | 28 | public function getEnvironment(): Environment 29 | { 30 | return $this->environment; 31 | } 32 | 33 | public function newPaymentRequest(string $platformToken, string $userToken, string $bankAccountToken, array $attributes = []): PaymentRequest 34 | { 35 | $paymentRequest = new PaymentRequest($this); 36 | 37 | $paymentRequest->platformToken = $platformToken; 38 | $paymentRequest->userToken = $userToken; 39 | $paymentRequest->bankAccountToken = $bankAccountToken; 40 | 41 | $paymentRequest->setAttributes($attributes); 42 | 43 | return $paymentRequest; 44 | } 45 | 46 | public function persistPaymentRequest(PaymentRequest $paymentRequest) 47 | { 48 | $response = $this->environment->send(new CreatePaymentRequestRequest($paymentRequest)); 49 | 50 | $paymentRequest->setAttributes($response->getData()); 51 | } 52 | 53 | public function paymentRequest(string $platformToken, string $userToken, string $paymentRequestToken): PaymentRequest 54 | { 55 | $response = $this->environment->send(new FetchPaymentRequestRequest($platformToken, $userToken, $paymentRequestToken)); 56 | 57 | $paymentRequest = new PaymentRequest($this); 58 | 59 | $paymentRequest->setAttributes($response->getData()); 60 | 61 | return $paymentRequest; 62 | } 63 | 64 | /** 65 | * @return PaymentRequest[] 66 | */ 67 | public function paymentRequests(string $platformToken, string $userToken, int $offset, int $limit, DateTimeInterface $fromDate = null, DateTimeInterface $toDate = null): array 68 | { 69 | $response = $this->environment->send(new FetchPaymentRequestsRequest($platformToken, $userToken, $offset, $limit, $fromDate, $toDate)); 70 | 71 | $paymentRequests = []; 72 | foreach ($response->getData()['paymentRequests'] as $paymentRequestData) { 73 | $paymentRequest = new PaymentRequest($this); 74 | 75 | $paymentRequest->setAttributes($paymentRequestData); 76 | 77 | $paymentRequests[] = $paymentRequest; 78 | } 79 | 80 | return $paymentRequests; 81 | } 82 | 83 | public function newPlatform(array $attributes = []): Platform 84 | { 85 | $platform = new Platform($this); 86 | 87 | $platform->setAttributes($attributes); 88 | 89 | return $platform; 90 | } 91 | 92 | public function persistPlatform(Platform $platform) 93 | { 94 | $response = $this->environment->send(new CreatePlatformRequest($platform)); 95 | 96 | $platform->setAttributes($response->getData()); 97 | } 98 | 99 | /** 100 | * @return Platform[] 101 | */ 102 | public function platforms(): array 103 | { 104 | $response = $this->environment->send(new FetchPlatformsRequest); 105 | 106 | $platforms = []; 107 | foreach ($response->getData() as $platformData) { 108 | $platform = new Platform($this); 109 | 110 | $platform->setAttributes($platformData); 111 | 112 | $platforms[] = $platform; 113 | } 114 | 115 | return $platforms; 116 | } 117 | 118 | public function newUser(string $platformToken, array $attributes = []): User 119 | { 120 | $user = new User($this); 121 | 122 | $user->platformToken = $platformToken; 123 | 124 | $user->setAttributes($attributes); 125 | 126 | return $user; 127 | } 128 | 129 | public function persistUser(User $user) 130 | { 131 | $response = $this->environment->send(new CreateUserRequest($user)); 132 | 133 | $user->setAttributes($response->getData()); 134 | } 135 | 136 | /** 137 | * @return User[] 138 | */ 139 | public function users(string $platformToken): array 140 | { 141 | $response = $this->environment->send(new FetchUsersRequest($platformToken)); 142 | 143 | $users = []; 144 | foreach ($response->getData() as $userData) { 145 | $user = new User($this); 146 | 147 | $user->setAttributes($userData); 148 | 149 | $users[] = $user; 150 | } 151 | 152 | return $users; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Requests/AbstractRequest.php: -------------------------------------------------------------------------------- 1 | parameters; 26 | } 27 | 28 | public function getPayload(): ?array 29 | { 30 | return $this->payload; 31 | } 32 | 33 | public function getRequestOptions(): ?array 34 | { 35 | return []; 36 | } 37 | 38 | abstract public function getMethod(): string; 39 | abstract public function getUri(): string; 40 | } 41 | -------------------------------------------------------------------------------- /src/Requests/CreatePaymentRequestRequest.php: -------------------------------------------------------------------------------- 1 | paymentRequest = $paymentRequest; 16 | } 17 | 18 | public function getMethod(): string 19 | { 20 | return 'POST'; 21 | } 22 | 23 | public function getUri(): string 24 | { 25 | return "v1/tikkie/platforms/{$this->paymentRequest->platformToken}/users/{$this->paymentRequest->userToken}/bankaccounts/{$this->paymentRequest->bankAccountToken}/paymentrequests"; 26 | } 27 | 28 | public function getPayload(): ?array 29 | { 30 | $paymentRequest = $this->paymentRequest; 31 | 32 | return [ 33 | 'amountInCents' => $paymentRequest->amountInCents, 34 | 'currency' => $paymentRequest->currency, 35 | 'description' => $paymentRequest->description, 36 | 'externalId' => $paymentRequest->externalId 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Requests/CreatePlatformRequest.php: -------------------------------------------------------------------------------- 1 | platform = $platform; 16 | } 17 | 18 | public function getMethod(): string 19 | { 20 | return 'POST'; 21 | } 22 | 23 | public function getUri(): string 24 | { 25 | return 'v1/tikkie/platforms'; 26 | } 27 | 28 | public function getPayload(): ?array 29 | { 30 | $platform = $this->platform; 31 | 32 | return [ 33 | 'email' => $platform->email, 34 | 'name' => $platform->name, 35 | 'notificationUrl' => $platform->notificationUrl, 36 | 'phoneNumber' => $platform->phoneNumber, 37 | 'platformUsage' => $platform->platformUsage 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Requests/CreateUserRequest.php: -------------------------------------------------------------------------------- 1 | user = $user; 16 | } 17 | 18 | public function getMethod(): string 19 | { 20 | return 'POST'; 21 | } 22 | 23 | public function getUri(): string 24 | { 25 | return "v1/tikkie/platforms/{$this->user->platformToken}/users"; 26 | } 27 | 28 | public function getPayload(): ?array 29 | { 30 | $user = $this->user; 31 | 32 | return [ 33 | 'name' => $user->name, 34 | 'phoneNumber' => $user->phoneNumber, 35 | 'iban' => $user->iban, 36 | 'bankAccountLabel' => $user->bankAccountLabel 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Requests/FetchPaymentRequestRequest.php: -------------------------------------------------------------------------------- 1 | platformToken = $platformToken; 24 | $this->userToken = $userToken; 25 | $this->paymentRequestToken = $paymentRequestToken; 26 | } 27 | 28 | public function getMethod(): string 29 | { 30 | return 'GET'; 31 | } 32 | 33 | public function getUri(): string 34 | { 35 | return "v1/tikkie/platforms/{$this->platformToken}/users/{$this->userToken}/paymentrequests/{$this->paymentRequestToken}"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Requests/FetchPaymentRequestsRequest.php: -------------------------------------------------------------------------------- 1 | platformToken = $platformToken; 23 | $this->userToken = $userToken; 24 | 25 | $params = compact('offset', 'limit'); 26 | 27 | if ($fromDate) { 28 | $params['fromDate'] = $this->formatDateTime($fromDate); 29 | } 30 | 31 | if ($toDate) { 32 | $params['toDate'] = $this->formatDateTime($toDate); 33 | } 34 | 35 | $this->parameters = $params; 36 | } 37 | 38 | public function getMethod(): string 39 | { 40 | return 'GET'; 41 | } 42 | 43 | public function getUri(): string 44 | { 45 | return "v1/tikkie/platforms/{$this->platformToken}/users/{$this->userToken}/paymentrequests"; 46 | } 47 | 48 | /** 49 | * Use UTC time zone and return ISO-8601 format. 50 | */ 51 | protected function formatDateTime(DateTimeInterface $date): string 52 | { 53 | return (new DateTime) 54 | ->setTimestamp($date->getTimestamp()) 55 | ->setTimezone(new DateTimeZone('UTC')) 56 | ->format('Y-m-d\TH:i:s\Z'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Requests/FetchPlatformsRequest.php: -------------------------------------------------------------------------------- 1 | platformToken = $platformToken; 14 | } 15 | 16 | public function getMethod(): string 17 | { 18 | return 'GET'; 19 | } 20 | 21 | public function getUri(): string 22 | { 23 | return "v1/tikkie/platforms/{$this->platformToken}/users"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | hasHeader(static::TRACE_ID_HEADER)) { 27 | throw new ResponseException("Trace ID header missing"); 28 | } 29 | 30 | $this->traceId = $response->getHeader(static::TRACE_ID_HEADER); 31 | 32 | if (($this->data = json_decode($response->getBody(), true)) === null) { 33 | throw new ResponseException("Could not decode response"); 34 | } 35 | } 36 | 37 | public function getData(): array 38 | { 39 | return $this->data; 40 | } 41 | 42 | public function getTraceId(): string 43 | { 44 | return $this->traceId; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/CreatePaymentRequestTest.php: -------------------------------------------------------------------------------- 1 | setMockHttpResponse(['AccessToken.txt', 'CreatePaymentRequestSuccess.txt']); 13 | 14 | $paymentRequest = $this->newPaymentRequest()->save(); 15 | 16 | $this->assertEquals('https://pay.here.com/123', $paymentRequest->paymentRequestUrl); 17 | $this->assertEquals('paymentrequesttoken1', $paymentRequest->paymentRequestToken); 18 | $this->assertEquals('Invoice: 4567', $paymentRequest->externalId); 19 | 20 | $request = $this->history[1]['request']; 21 | 22 | $this->assertEquals('POST', $request->getMethod()); 23 | $this->assertEquals('https://api.abnamro.com/v1/tikkie/platforms/platformtoken1/users/usertoken1/bankaccounts/bankaccounttoken1/paymentrequests', $request->getUri()); 24 | $this->assertEquals('{"amountInCents":"1250","currency":"EUR","description":"Thank you","externalId":"Order 1234"}', $request->getBody()); 25 | } 26 | 27 | public function testCreatePaymentRequestFailed() 28 | { 29 | $this->setMockHttpResponse(['AccessToken.txt', 'ValidationError.txt']); 30 | 31 | $this->expectException(RequestException::class); 32 | $this->expectExceptionMessage("[ERR_1100_004] Field validation error | traceId: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 | (see https://developer.abnamro.com/api/tikkie/technical-details)"); 33 | 34 | $this->newPaymentRequest()->save(); 35 | } 36 | 37 | protected function newPaymentRequest(): PaymentRequest 38 | { 39 | return $this->tikkie->newPaymentRequest('platformtoken1', 'usertoken1', 'bankaccounttoken1', [ 40 | 'amountInCents' => '1250', 41 | 'currency' => 'EUR', 42 | 'description' => 'Thank you', 43 | 'externalId' => 'Order 1234' 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/CreatePlatformTest.php: -------------------------------------------------------------------------------- 1 | setMockHttpResponse(['AccessToken.txt', 'CreatePlatformSuccess.txt']); 13 | 14 | $platform = $this->newPlatform()->save(); 15 | 16 | $this->assertEquals('platformtoken1', $platform->platformToken); 17 | $this->assertEquals('x@yz.com', $platform->email); 18 | $this->assertEquals('NewPlatform', $platform->name); 19 | $this->assertEquals('0601234567', $platform->phoneNumber); 20 | $this->assertEquals(Platform::USAGE_TYPE_MYSELF, $platform->platformUsage); 21 | $this->assertNull($platform->notificationUrl); 22 | $this->assertTrue($platform->isActive()); 23 | 24 | $request = $this->history[1]['request']; 25 | 26 | $this->assertEquals('POST', $request->getMethod()); 27 | $this->assertEquals('https://api.abnamro.com/v1/tikkie/platforms', $request->getUri()); 28 | $this->assertEquals('{"email":"tikkie@example.com","name":"MyPlatform","notificationUrl":"","phoneNumber":"0612345678","platformUsage":"PAYMENT_REQUEST_FOR_MYSELF"}', $request->getBody()); 29 | } 30 | 31 | public function testCreatePlatformFailed() 32 | { 33 | $this->setMockHttpResponse(['AccessToken.txt', 'ValidationError.txt']); 34 | 35 | $this->expectException(RequestException::class); 36 | $this->expectExceptionMessage("[ERR_1100_004] Field validation error | traceId: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 | (see https://developer.abnamro.com/api/tikkie/technical-details)"); 37 | 38 | $this->newPlatform()->save(); 39 | } 40 | 41 | protected function newPlatform(): Platform 42 | { 43 | return $this->tikkie->newPlatform([ 44 | 'name' => 'MyPlatform', 45 | 'phoneNumber' => '0612345678', 46 | 'platformUsage' => Platform::USAGE_TYPE_MYSELF, 47 | 'email' => 'tikkie@example.com', 48 | 'notificationUrl' => '' 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/CreateUserTest.php: -------------------------------------------------------------------------------- 1 | setMockHttpResponse(['AccessToken.txt', 'CreateUserSuccess.txt']); 14 | 15 | $user = $this->newUser()->save(); 16 | 17 | $this->assertEquals('YourLabel', $user->bankAccountLabel); 18 | $this->assertEquals('NL00BANK123456789', $user->iban); 19 | $this->assertEquals('NewUser', $user->name); 20 | $this->assertEquals('06123456789', $user->phoneNumber); 21 | $this->assertEquals('platformtoken1', $user->platformToken); 22 | $this->assertEquals(User::STATUS_ACTIVE, $user->status); 23 | $this->assertEquals('usertoken1', $user->userToken); 24 | $this->assertTrue($user->isActive()); 25 | $this->assertCount(1, $user->bankAccounts); 26 | 27 | $bankAccount = $user->bankAccounts[0]; 28 | 29 | $this->assertInstanceOf(BankAccount::class, $bankAccount); 30 | $this->assertEquals('Personal account', $bankAccount->bankAccountLabel); 31 | $this->assertEquals('bankaccounttoken1', $bankAccount->bankAccountToken); 32 | $this->assertEquals('NL02ABNA0123456789', $bankAccount->iban); 33 | 34 | $request = $this->history[1]['request']; 35 | 36 | $this->assertEquals('POST', $request->getMethod()); 37 | $this->assertEquals('https://api.abnamro.com/v1/tikkie/platforms/platformtoken1/users', $request->getUri()); 38 | $this->assertEquals('{"name":"ExamplePlatform","phoneNumber":"06123456789","iban":"NL00BANK123456789","bankAccountLabel":"YourLabel"}', $request->getBody()); 39 | } 40 | 41 | public function testCreateUserFailed() 42 | { 43 | $this->setMockHttpResponse(['AccessToken.txt', 'ValidationError.txt']); 44 | 45 | $this->expectException(RequestException::class); 46 | $this->expectExceptionMessage("[ERR_1100_004] Field validation error | traceId: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 | (see https://developer.abnamro.com/api/tikkie/technical-details)"); 47 | 48 | $this->newUser()->save(); 49 | } 50 | 51 | protected function newUser(): User 52 | { 53 | return $this->tikkie->newUser('platformtoken1', [ 54 | 'name' => 'ExamplePlatform', 55 | 'phoneNumber' => '06123456789', 56 | 'iban' => 'NL00BANK123456789', 57 | 'bankAccountLabel' => 'YourLabel' 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/EnvironmentTest.php: -------------------------------------------------------------------------------- 1 | expectException(AccessTokenException::class); 12 | 13 | $this->tikkie->getEnvironment()->loadPrivateKeyFromString(''); 14 | 15 | $this->tikkie->newPlatform()->save(); 16 | } 17 | 18 | public function testInvalidApiKeyHandling() 19 | { 20 | $this->setMockHttpResponse('InvalidApiKey.txt'); 21 | 22 | $this->expectException(AccessTokenException::class); 23 | $this->expectExceptionMessage("[ERR_2005_001] The API Key is invalid | traceId: 98de8459-377d-4834-a0a2-079f94f0f43d | (see https://developer.abnamro.com/get-started#obtaining-an-api-key)"); 24 | 25 | $this->tikkie->newPlatform()->save(); 26 | } 27 | 28 | public function testHeadersOnApiRequest() 29 | { 30 | $this->setMockHttpResponse(['AccessToken.txt', 'CreatePlatformSuccess.txt']); 31 | 32 | $this->tikkie->newPlatform()->save(); 33 | 34 | // AccessTokenRequest 35 | $request = $this->history[0]['request']; 36 | $this->assertEquals(static::API_KEY, $request->getHeader('API-Key')[0]); 37 | 38 | // CreatePlatformRequest 39 | $request = $this->history[1]['request']; 40 | $this->assertEquals(static::API_KEY, $request->getHeader('API-Key')[0]); 41 | $this->assertTrue($request->hasHeader('Authorization')); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/FetchPaymentRequestTest.php: -------------------------------------------------------------------------------- 1 | setMockHttpResponse(['AccessToken.txt', 'FetchPaymentRequestSuccess.txt']); 15 | 16 | $paymentRequest = $this->tikkie->paymentRequest('platformtoken1', 'usertoken1', 'paymentrequesttoken1'); 17 | 18 | $this->assertEquals('123', $paymentRequest->amountInCents); 19 | $this->assertFalse($paymentRequest->bankAccountYieldedTooFast); 20 | $this->assertEquals('1465495200', $paymentRequest->created->getTimestamp()); 21 | $this->assertEquals('EUR', $paymentRequest->currency); 22 | $this->assertEquals('Last night\'s dinner', $paymentRequest->description); 23 | $this->assertNull($paymentRequest->expired); 24 | $this->assertEquals('paymentrequesttoken1', $paymentRequest->paymentRequestToken); 25 | $this->assertEquals('Invoice: 4567', $paymentRequest->externalId); 26 | $this->assertEquals(PaymentRequest::STATUS_OPEN, $paymentRequest->status); 27 | $this->assertCount(1, $paymentRequest->payments); 28 | 29 | $payment = $paymentRequest->payments[0]; 30 | 31 | $this->assertInstanceOf(Payment::class, $payment); 32 | $this->assertEquals('EUR', $payment->amountCurrency); 33 | $this->assertEquals('123', $payment->amountInCents); 34 | $this->assertEquals('E. Xample', $payment->counterPartyName); 35 | $this->assertEquals('Payment for tikkie', $payment->description); 36 | $this->assertEquals(Payment::STATUS_PAID, $payment->onlinePaymentStatus); 37 | $this->assertEquals('paymenttoken1', $payment->paymentToken); 38 | $this->assertEquals('1465495200', $payment->created->getTimestamp()); 39 | $this->assertTrue($payment->isPaid()); 40 | 41 | $request = $this->history[1]['request']; 42 | 43 | $this->assertEquals('GET', $request->getMethod()); 44 | $this->assertEquals('https://api.abnamro.com/v1/tikkie/platforms/platformtoken1/users/usertoken1/paymentrequests/paymentrequesttoken1', $request->getUri()); 45 | } 46 | 47 | public function testFetchPaymentRequestFailed() 48 | { 49 | $this->setMockHttpResponse(['AccessToken.txt', 'ValidationError.txt']); 50 | 51 | $this->expectException(RequestException::class); 52 | $this->expectExceptionMessage("[ERR_1100_004] Field validation error | traceId: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 | (see https://developer.abnamro.com/api/tikkie/technical-details)"); 53 | 54 | $this->tikkie->paymentRequest('platformtoken1', 'usertoken1', 'paymentrequesttoken1'); 55 | } 56 | 57 | // Due to a Tikkie bug, the API may return epoch timestamps with milliseconds instead of a ISO-8601 formatted string. 58 | // I reported this on 24-04-2019. 59 | public function testCreatedDateCanBeTimestampWithMilliseconds() 60 | { 61 | $payment = new Payment($this->tikkie); 62 | $payment->setAttributes(['created' => '1554957274674']); 63 | 64 | $this->assertInstanceOf(DateTimeImmutable::class, $payment->created); 65 | $this->assertEquals('1554957274', $payment->created->getTimestamp()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/FetchPaymentRequestsTest.php: -------------------------------------------------------------------------------- 1 | setMockHttpResponse(['AccessToken.txt', 'FetchPaymentRequestsSuccess.txt']); 16 | 17 | $timezone = new DateTimeZone('Europe/Amsterdam'); 18 | 19 | $from = new DateTime('2018-06-01 13:00', $timezone); 20 | $to = new DateTime('2018-06-05 01:00', $timezone); 21 | 22 | $paymentRequests = $this->tikkie->paymentRequests('platformtoken1', 'usertoken1', 0, 10, $from, $to); 23 | $paymentRequest = $paymentRequests[0]; 24 | 25 | $this->assertCount(1, $paymentRequests); 26 | $this->assertInstanceOf(PaymentRequest::class, $paymentRequest); 27 | $this->assertEquals('123', $paymentRequest->amountInCents); 28 | $this->assertFalse($paymentRequest->bankAccountYieldedTooFast); 29 | $this->assertEquals('1465495200', $paymentRequest->created->getTimestamp()); 30 | $this->assertEquals('EUR', $paymentRequest->currency); 31 | $this->assertEquals('Last night\'s dinner', $paymentRequest->description); 32 | $this->assertNull($paymentRequest->expired); 33 | $this->assertEquals('paymentrequesttoken1', $paymentRequest->paymentRequestToken); 34 | $this->assertEquals('Invoice: 4567', $paymentRequest->externalId); 35 | $this->assertEquals(PaymentRequest::STATUS_OPEN, $paymentRequest->status); 36 | $this->assertCount(1, $paymentRequest->payments); 37 | 38 | $payment = $paymentRequest->payments[0]; 39 | 40 | $this->assertInstanceOf(Payment::class, $payment); 41 | $this->assertEquals('EUR', $payment->amountCurrency); 42 | $this->assertEquals('123', $payment->amountInCents); 43 | $this->assertEquals('E. Xample', $payment->counterPartyName); 44 | $this->assertEquals('Payment for tikkie', $payment->description); 45 | $this->assertEquals(Payment::STATUS_NEW, $payment->onlinePaymentStatus); 46 | $this->assertEquals('paymenttoken1', $payment->paymentToken); 47 | $this->assertEquals('1465495200', $payment->created->getTimestamp()); 48 | $this->assertFalse($payment->isPaid()); 49 | 50 | $request = $this->history[1]['request']; 51 | 52 | $this->assertEquals('GET', $request->getMethod()); 53 | $this->assertEquals('https://api.abnamro.com/v1/tikkie/platforms/platformtoken1/users/usertoken1/paymentrequests?offset=0&limit=10&fromDate=2018-06-01T11%3A00%3A00Z&toDate=2018-06-04T23%3A00%3A00Z', $request->getUri()); 54 | } 55 | 56 | public function testFetchPaymentRequestsFailed() 57 | { 58 | $this->setMockHttpResponse(['AccessToken.txt', 'ValidationError.txt']); 59 | 60 | $this->expectException(RequestException::class); 61 | $this->expectExceptionMessage("[ERR_1100_004] Field validation error | traceId: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 | (see https://developer.abnamro.com/api/tikkie/technical-details)"); 62 | 63 | $this->tikkie->paymentRequests('platformtoken1', 'usertoken1', 0, 10); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/FetchPlatformsTest.php: -------------------------------------------------------------------------------- 1 | setMockHttpResponse(['AccessToken.txt', 'FetchPlatformsSuccess.txt']); 13 | 14 | $platforms = $this->tikkie->platforms(); 15 | $platform = $platforms[0]; 16 | 17 | $this->assertCount(1, $platforms); 18 | $this->assertInstanceOf(Platform::class, $platform); 19 | $this->assertEquals('platformtoken1', $platform->platformToken); 20 | $this->assertEquals('x@yz.com', $platform->email); 21 | $this->assertEquals('NewPlatform', $platform->name); 22 | $this->assertEquals('0601234567', $platform->phoneNumber); 23 | $this->assertEquals(Platform::USAGE_TYPE_MYSELF, $platform->platformUsage); 24 | $this->assertNull($platform->notificationUrl); 25 | $this->assertTrue($platform->isActive()); 26 | 27 | $request = $this->history[1]['request']; 28 | 29 | $this->assertEquals('GET', $request->getMethod()); 30 | $this->assertEquals('https://api.abnamro.com/v1/tikkie/platforms', $request->getUri()); 31 | $this->assertEmpty($request->getUri()->getQuery()); 32 | } 33 | 34 | public function testFetchUsersFailed() 35 | { 36 | $this->setMockHttpResponse(['AccessToken.txt', 'ValidationError.txt']); 37 | 38 | $this->expectException(RequestException::class); 39 | $this->expectExceptionMessage("[ERR_1100_004] Field validation error | traceId: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 | (see https://developer.abnamro.com/api/tikkie/technical-details)"); 40 | 41 | $this->tikkie->platforms(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/FetchUsersTest.php: -------------------------------------------------------------------------------- 1 | setMockHttpResponse(['AccessToken.txt', 'FetchUsersSuccess.txt']); 14 | 15 | $users = $this->tikkie->users('platformtoken1'); 16 | $user = $users[0]; 17 | 18 | $this->assertCount(1, $users); 19 | $this->assertInstanceOf(User::class, $user); 20 | $this->assertEquals('NewUser', $user->name); 21 | $this->assertEquals(User::STATUS_ACTIVE, $user->status); 22 | $this->assertEquals('usertoken1', $user->userToken); 23 | $this->assertTrue($user->isActive()); 24 | $this->assertCount(1, $user->bankAccounts); 25 | 26 | $bankAccount = $user->bankAccounts[0]; 27 | 28 | $this->assertInstanceOf(BankAccount::class, $bankAccount); 29 | $this->assertEquals('Personal account', $bankAccount->bankAccountLabel); 30 | $this->assertEquals('bankaccounttoken1', $bankAccount->bankAccountToken); 31 | $this->assertEquals('NL02ABNA0123456789', $bankAccount->iban); 32 | 33 | $request = $this->history[1]['request']; 34 | 35 | $this->assertEquals('GET', $request->getMethod()); 36 | $this->assertEquals('https://api.abnamro.com/v1/tikkie/platforms/platformtoken1/users', $request->getUri()); 37 | $this->assertEmpty($request->getUri()->getQuery()); 38 | } 39 | 40 | public function testFetchUsersFailed() 41 | { 42 | $this->setMockHttpResponse(['AccessToken.txt', 'ValidationError.txt']); 43 | 44 | $this->expectException(RequestException::class); 45 | $this->expectExceptionMessage("[ERR_1100_004] Field validation error | traceId: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 | (see https://developer.abnamro.com/api/tikkie/technical-details)"); 46 | 47 | $this->tikkie->users('platformtoken1'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Mock/AccessToken.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | { 8 | "access_token": "accesstoken1", 9 | "expires_in": "60", 10 | "scope": "tikkie", 11 | "token_type": "Bearer" 12 | } -------------------------------------------------------------------------------- /tests/Mock/CreatePaymentRequestSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | { 8 | "paymentRequestUrl": "https://pay.here.com/123", 9 | "paymentRequestToken": "paymentrequesttoken1", 10 | "externalId": "Invoice: 4567" 11 | } -------------------------------------------------------------------------------- /tests/Mock/CreatePlatformSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | { 8 | "name": "NewPlatform", 9 | "platformToken": "platformtoken1", 10 | "phoneNumber": "0601234567", 11 | "email": "x@yz.com", 12 | "notificationUrl": null, 13 | "status": "ACTIVE", 14 | "platformUsage": "PAYMENT_REQUEST_FOR_MYSELF" 15 | } -------------------------------------------------------------------------------- /tests/Mock/CreateUserSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | { 8 | "userToken": "usertoken1", 9 | "name": "NewUser", 10 | "status": "ACTIVE", 11 | "bankAccounts": [ 12 | { 13 | "bankAccountToken": "bankaccounttoken1", 14 | "iban": "NL02ABNA0123456789", 15 | "bankAccountLabel": "Personal account" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/Mock/FetchPaymentRequestSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | { 8 | "paymentRequestToken": "paymentrequesttoken1", 9 | "amountInCents": "123", 10 | "currency": "EUR", 11 | "description": "Last night's dinner", 12 | "created": "2016-06-09T18:00:00.000Z", 13 | "expired": null, 14 | "status": "OPEN", 15 | "bankAccountYieldedTooFast": false, 16 | "externalId": "Invoice: 4567", 17 | "payments": [ 18 | { 19 | "paymentToken": "paymenttoken1", 20 | "counterPartyName": "E. Xample", 21 | "amountInCents": "123", 22 | "amountCurrency": "EUR", 23 | "description": "Payment for tikkie", 24 | "created": "2016-06-09T18:00:00.000Z", 25 | "onlinePaymentStatus": "PAID" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /tests/Mock/FetchPaymentRequestsSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | { 8 | "paymentRequests": [ 9 | { 10 | "paymentRequestToken": "paymentrequesttoken1", 11 | "amountInCents": "123", 12 | "currency": "EUR", 13 | "description": "Last night's dinner", 14 | "created": "2016-06-09T18:00:00.000Z", 15 | "expired": null, 16 | "status": "OPEN", 17 | "bankAccountYieldedTooFast": false, 18 | "externalId": "Invoice: 4567", 19 | "payments": [ 20 | { 21 | "paymentToken": "paymenttoken1", 22 | "counterPartyName": "E. Xample", 23 | "amountInCents": "123", 24 | "amountCurrency": "EUR", 25 | "description": "Payment for tikkie", 26 | "created": "2016-06-09T18:00:00.000Z", 27 | "onlinePaymentStatus": "NEW" 28 | } 29 | ] 30 | } 31 | ], 32 | "totalElements": 1 33 | } -------------------------------------------------------------------------------- /tests/Mock/FetchPlatformsSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | [ 8 | { 9 | "name": "NewPlatform", 10 | "platformToken": "platformtoken1", 11 | "phoneNumber": "0601234567", 12 | "email": "x@yz.com", 13 | "notificationUrl": null, 14 | "status": "ACTIVE", 15 | "platformUsage": "PAYMENT_REQUEST_FOR_MYSELF" 16 | } 17 | ] -------------------------------------------------------------------------------- /tests/Mock/FetchUsersSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | [ 8 | { 9 | "userToken": "usertoken1", 10 | "name": "NewUser", 11 | "status": "ACTIVE", 12 | "bankAccounts": [ 13 | { 14 | "bankAccountToken": "bankaccounttoken1", 15 | "iban": "NL02ABNA0123456789", 16 | "bankAccountLabel": "Personal account" 17 | } 18 | ] 19 | } 20 | ] -------------------------------------------------------------------------------- /tests/Mock/InvalidApiKey.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 401 Unauthorized 2 | Date: Tue, 19 Jun 2018 09:20:42 GMT 3 | Content-Type: application/json 4 | Connection: keep-alive 5 | Trace-Id: 98de8459-377d-4834-a0a2-079f94f0f43d 6 | 7 | { 8 | "errors": [ 9 | { 10 | "code": "ERR_2005_001", 11 | "message": "The API Key is invalid", 12 | "reference": "https://developer.abnamro.com/get-started#obtaining-an-api-key", 13 | "traceId": "98de8459-377d-4834-a0a2-079f94f0f43d", 14 | "status": 401, 15 | "category":"INVALID_API_KEY" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/Mock/ValidationError.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 400 Bad Request 2 | Date: Tue, 19 Jun 2018 10:24:12 GMT 3 | Content-Type: application/json 4 | Transfer-Encoding: chunked 5 | Connection: keep-alive 6 | Server: Apache-Coyote/1.1 7 | Trace-Id: 3cbf4bc9-108c-4e02-ad6e-937c79d875e3 8 | X-Application-Context: application 9 | X-Content-Type-Options: nosniff 10 | X-Frame-Options: sameorigin 11 | X-XSS-Protection: 1; mode=block 12 | 13 | { 14 | "errors": [ 15 | { 16 | "code": "ERR_1100_004", 17 | "message": "Field validation error", 18 | "reference": "https://developer.abnamro.com/api/tikkie/technical-details", 19 | "traceId": "3cbf4bc9-108c-4e02-ad6e-937c79d875e3", 20 | "status": 400, 21 | "category": "BAD_REQUEST" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /tests/Mock/private_rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAuLGN/Wcdr4s7b9rKJiWpi0PHa4kOsEBbAh1oS78ETzpgzVl6 3 | K0pQ96tZQpx90VqnhCh2eyycCmSKBtd9SKVswsHO21mw/Xh5S0mEd0g9Fl5f8XVr 4 | +GGSL5wxUhgJcP1ZOtsh3yUt14hQZk0/CF5yf0Y8M3u92Tq/LC6OVwUQC5qAPcOo 5 | tJv4yhqHCCe0RK+QxYZ1Rk7A0zbhW9n/eatf0qSN3pRKIPFUmAIgiJ1i9i362PJ6 6 | /Ri6nyeeXtwuqzxZ6ryA+L1WXNUswOoU3HXBWiRVsEGt0o68btQan1T680qfuevP 7 | usAd6jkQkHwehYbMAjWGRUGxgeEDUgGN2KndOwIDAQABAoIBAAGZD6hslvYo1zfN 8 | hcg0tTXOPVBx3B2sQN1Q0dEt8uSYlbPRHVx4hSDPa7NG1VPVW/OpwfSo2yS/3+3V 9 | OXFCdSFuOdLuALkArNP469t4VYb3BA97u5ZnibM9v7N9H7/JgVcgBDgBA1eFB2UL 10 | MAtrfcQaIHU1kdvqRTMlp/eaxJvFE6m7W1fKqNtfjWvsG8eOPK09tIkreQ8RES4p 11 | SUjVLMpwfDBEysjE0m+QtOrtBzENZ8AK/Jj3kYlgjJLorcUkwStG/QzDaKthPxXO 12 | pDC8rU28B4unAidPYpSzFVpgWek4+wNu0bp1H7CFOm9/fFV4p2a9nElIwiI5H2JG 13 | TvmNFIkCgYEA2trtt2wL1Grsx0OmoV/BFWBT3p2L2WP1JzyVOzYV5gttyDgK123e 14 | jrbqJhTJzWR9/r5ZmlzlWTgOe5OUsSENQgceDl24aF+c3xCjqhvEvaNgHkyie7wV 15 | GxOZbtouoxAV9aBN2tr6TKmpZeI4wjwefAxpnxXWWSbcuFd1NcvM9mUCgYEA2ApV 16 | O0Mk2A25Wt5zqdi7zyiGN3MHbbzbk6RADObRsfAtAc+hzhm9zHoWZ+GQtZbV//cM 17 | LAwItk8hSPb16frSZRTC3xPQXEAUbNFbo3iC8XfbkWGQ1daSD3vjggZMl0oyTOeT 18 | f8+V5GA0CEO+shdL7OdX2wrT5303CciSGc6H+x8CgYEAwg6D4DM23hgdcabD1q1w 19 | KMMsOuWZfp5KX4sxi7ouerfBlVpUCqeKQyNfxXqDYdLsgXhJPT2Hw6+X7+1xWApF 20 | ljl3Ziel/y5gi85WQNy0e66k6Njs7ihc0pBzaT6fQclen1iQEDJA5L8/zG5mA5yJ 21 | L1dWRFB5qr1x0ycBcD0QtZ0CgYBMIpgW4nrBiVShKXQpjy/gua9TKz6a9cuMJH5O 22 | DaoowzOZR8sGDxxMS1pKEbwlwpp4MmAfxb3sQcbBKAdSBgrPhJmIdDshx5NrJfzd 23 | h0LeRhcjDPc15QBkZIyw7fdf+c3wPExG5qk0apLrtrQlnwAq5PyZPR+gnQggcobK 24 | gXZ6kQKBgQC7IW0jrjjW+fx262yGMDa+VZRXN/xctupj+n/GWtLA0Pq/fy3/2CqY 25 | MMHsYlidjcurMvmX9HX9ft2RqdPBV/sEr4pR2Vietxje7xFZPNiy0z45UHYQ1zfn 26 | /iJkEoPKzaKqDBXGRi9E+GhG5KxClY5dZniPhu6GHUWdyq7uFDxLUw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/Mock/public_rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuLGN/Wcdr4s7b9rKJiWp 3 | i0PHa4kOsEBbAh1oS78ETzpgzVl6K0pQ96tZQpx90VqnhCh2eyycCmSKBtd9SKVs 4 | wsHO21mw/Xh5S0mEd0g9Fl5f8XVr+GGSL5wxUhgJcP1ZOtsh3yUt14hQZk0/CF5y 5 | f0Y8M3u92Tq/LC6OVwUQC5qAPcOotJv4yhqHCCe0RK+QxYZ1Rk7A0zbhW9n/eatf 6 | 0qSN3pRKIPFUmAIgiJ1i9i362PJ6/Ri6nyeeXtwuqzxZ6ryA+L1WXNUswOoU3HXB 7 | WiRVsEGt0o68btQan1T680qfuevPusAd6jkQkHwehYbMAjWGRUGxgeEDUgGN2Knd 8 | OwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | history = []; 32 | $this->tikkie = $this->createPHPTikkie(); 33 | } 34 | 35 | protected function createPHPTikkie(array $options = []): PHPTikkie 36 | { 37 | $environment = new Environment(static::API_KEY, false, $options); 38 | $environment->loadPrivateKey(static::MOCK_PATH.'/private_rsa.pem'); 39 | 40 | return new PHPTikkie($environment); 41 | } 42 | 43 | protected function getMockHttpResponse(string $path): ResponseInterface 44 | { 45 | return \GuzzleHttp\Psr7\parse_response(file_get_contents(static::MOCK_PATH.'/'.$path)); 46 | } 47 | 48 | /** 49 | * @param string|array $paths 50 | */ 51 | protected function setMockHttpResponse($paths): void 52 | { 53 | $history = Middleware::history($this->history); 54 | 55 | $responses = []; 56 | foreach ((array) $paths as $path) { 57 | $responses[] = $this->getMockHttpResponse($path); 58 | } 59 | 60 | $mock = new MockHandler($responses); 61 | 62 | $stack = HandlerStack::create($mock); 63 | $stack->push($history); 64 | 65 | $this->tikkie = $this->createPHPTikkie([ 66 | 'handler' => $stack 67 | ]); 68 | } 69 | } 70 | --------------------------------------------------------------------------------