├── .github └── workflows │ └── test.yaml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── psalm-baseline.xml ├── psalm.xml.dist ├── src ├── Address.php ├── AuthenticationRequestHeaderProvider.php ├── CurrencyAwareTrait.php ├── Customer.php ├── Gateway.php ├── GatewayInterface.php ├── Item.php ├── ItemBag.php ├── ItemInterface.php ├── Message │ ├── AbstractOrderRequest.php │ ├── AbstractRequest.php │ ├── AbstractResponse.php │ ├── AcknowledgeRequest.php │ ├── AcknowledgeResponse.php │ ├── AuthorizeRequest.php │ ├── AuthorizeResponse.php │ ├── CaptureRequest.php │ ├── CaptureResponse.php │ ├── ExtendAuthorizationRequest.php │ ├── ExtendAuthorizationResponse.php │ ├── FetchTransactionRequest.php │ ├── FetchTransactionResponse.php │ ├── ItemDataTrait.php │ ├── MerchantUrlsDataTrait.php │ ├── RefundRequest.php │ ├── RefundResponse.php │ ├── UpdateCustomerAddressRequest.php │ ├── UpdateCustomerAddressResponse.php │ ├── UpdateMerchantReferencesRequest.php │ ├── UpdateMerchantReferencesResponse.php │ ├── UpdateTransactionRequest.php │ ├── UpdateTransactionResponse.php │ ├── VoidRequest.php │ └── VoidResponse.php └── WidgetOptions.php └── tests ├── AuthenticationRequestHeaderProviderTest.php ├── ExpectedAuthorizationHeaderTrait.php ├── GatewayTest.php ├── ItemBagTest.php ├── ItemTest.php └── Message ├── AbstractRequestTest.php ├── AbstractResponseTest.php ├── AcknowledgeRequestTest.php ├── AcknowledgeResponseTest.php ├── AddressTest.php ├── AuthorizeRequestTest.php ├── AuthorizeResponseTest.php ├── CaptureRequestTest.php ├── CaptureResponseTest.php ├── CustomerTest.php ├── ExtendAuthorizationRequestTest.php ├── ExtendAuthorizationResponseTest.php ├── FetchTransactionRequestTest.php ├── FetchTransactionResponseTest.php ├── ItemDataTestTrait.php ├── MerchantUrlsDataTestTrait.php ├── RefundRequestTest.php ├── RefundResponseTest.php ├── RequestTestCase.php ├── UpdateCustomerAddressRequestTest.php ├── UpdateCustomerAddressResponseTest.php ├── UpdateMerchantReferencesRequestTest.php ├── UpdateTransactionRequestTest.php ├── VoidRequestTest.php ├── VoidResponseTest.php └── WidgetOptionsTest.php /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'master' 7 | tags-ignore: 8 | - '**' 9 | 10 | jobs: 11 | setup: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: 7.4 18 | coverage: none 19 | - uses: actions/cache@v2 20 | with: 21 | path: vendor 22 | key: php-7.4-vendor-${{ hashFiles('**/composer.json') }} 23 | restore-keys: php-7.4-vendor- 24 | - run: composer install --no-interaction --no-ansi 25 | - id: set-php-versions 26 | run: echo "::set-output name=php-versions::$(vendor/bin/devtools list:php-versions)" 27 | - id: set-tools 28 | run: echo "::set-output name=tools::$(vendor/bin/devtools list:enabled-tools)" 29 | outputs: 30 | php-versions: ${{ steps.set-php-versions.outputs.php-versions }} 31 | tools: ${{ steps.set-tools.outputs.tools }} 32 | 33 | test: 34 | needs: setup 35 | runs-on: ubuntu-latest 36 | strategy: 37 | matrix: 38 | php-version: ${{ fromJson(needs.setup.outputs.php-versions) }} 39 | tool: ${{ fromJson(needs.setup.outputs.tools) }} 40 | fail-fast: false 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: shivammathur/setup-php@v2 44 | with: 45 | php-version: ${{ matrix.php-version }} 46 | ini-values: date.timezone=Europe/Amsterdam, assert.exception=1, zend.assertions=1 47 | - uses: actions/cache@v2 48 | with: 49 | path: vendor 50 | key: php-${{ matrix.php-version }}-vendor-${{ hashFiles('**/composer.json') }} 51 | restore-keys: php-${{ matrix.php-version }}-vendor- 52 | - run: composer install --no-interaction --no-ansi 53 | 54 | - run: vendor/bin/devtools ${{ matrix.tool }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .phpunit.result.cache 4 | vendor 5 | composer.lock 6 | phpunit.xml 7 | /.phpcs-cache 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 MyOnlineStore B.V. 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 | # NO LONGER MAINTAINED 2 | This repository is no longer being maintained. 3 | 4 | # Omnipay: Klarna Checkout 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 6 | [![Scrutinizer Build](https://img.shields.io/scrutinizer/build/g/MyOnlineStore/omnipay-klarna-checkout.svg?style=flat-square)](https://github.com/MyOnlineStore/omnipay-klarna-checkout) 7 | [![Scrutinizer Coverage](https://img.shields.io/scrutinizer/coverage/g/MyOnlineStore/omnipay-klarna-checkout.svg?style=flat-square)](https://github.com/MyOnlineStore/omnipay-klarna-checkout) 8 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/MyOnlineStore/omnipay-klarna-checkout.svg?style=flat-square)](https://github.com/MyOnlineStore/omnipay-klarna-checkout) 9 | 10 | ## Introduction 11 | 12 | [Omnipay](https://github.com/thephpleague/omnipay) is a framework agnostic, multi-gateway payment 13 | processing library for PHP 5.6+. This package implements Klarna Checkout support for Omnipay. 14 | 15 | ## Installation 16 | 17 | To install, simply add it to your `composer.json` file: 18 | ```shell 19 | $ composer require myonlinestore/omnipay-klarna-checkout 20 | ``` 21 | 22 | ## Initialization 23 | 24 | First, create the Omnipay gateway: 25 | ```php 26 | $gateway = Omnipay\Omnipay::create('\MyOnlineStore\Omnipay\KlarnaCheckout\Gateway'); 27 | // or 28 | $gateway = new MyOnlineStore\Omnipay\KlarnaCheckout\Gateway(/* $httpClient, $httpRequest */); 29 | ``` 30 | Then, initialize it with the correct credentials: 31 | ```php 32 | $gateway->initialize([ 33 | 'username' => $username, 34 | 'secret' => $secret, 35 | 'api_region' => $region, // Optional, may be Gateway::API_VERSION_EUROPE (default) or Gateway::API_VERSION_NORTH_AMERICA 36 | 'testMode' => false // Optional, default: true 37 | ]); 38 | // or 39 | $gateway->setUsername($username); 40 | $gateway->setSecret($secret); 41 | $gateway->setApiRegion($region); 42 | ``` 43 | 44 | ## Usage 45 | 46 | For general usage instructions, please see the main [Omnipay](https://github.com/thephpleague/omnipay) 47 | repository. 48 | 49 | ### General flow 50 | 51 | 1. [Create a Klarna order](#authorize) 52 | 2. [Update transaction](#update-transaction) (if required) 53 | 3. [Render the Iframe](#render-iframe) 54 | 4. Respond to redirects to `checkoutUrl` or `confirmation_url` 55 | 5. Respond to [checkout callbacks](https://developers.klarna.com/api/#checkout-api-callbacks-callbacks) 56 | 6. Respond to the request to `push_url` (indicates order was completed by client) 57 | with a [ackowledgement](#acknowledge) 58 | 7. [Extend authorization](#extend-authorization) (if required) 59 | 8. [Update the merchant address](#update-merchant-address) (if required) 60 | 9. Perform one or more [capture(s)](#capture), [refund(s)](#refund) or [void](#void) operations 61 | 62 | ### Authorize 63 | 64 | To create a new order, use the `authorize` method: 65 | ```php 66 | $data = [ 67 | 'amount' => 100, 68 | 'tax_amount' => 20, 69 | 'currency' => 'SEK', 70 | 'locale' => 'SE', 71 | 'purchase_country' => 'SE', 72 | 73 | 'notify_url' => '', // https://developers.klarna.com/api/#checkout-api__ordermerchant_urls__validation 74 | 'return_url' => '', // https://developers.klarna.com/api/#checkout-api__ordermerchant_urls__checkout 75 | 'terms_url' => '', // https://developers.klarna.com/api/#checkout-api__ordermerchant_urls__terms 76 | 'validation_url' => '', // https://developers.klarna.com/api/#checkout-api__ordermerchant_urls__validation 77 | 78 | 'items' => [ 79 | [ 80 | 'type' => 'physical', 81 | 'name' => 'Shirt', 82 | 'quantity' => 1, 83 | 'tax_rate' => 25, 84 | 'price' => 100, 85 | 'unit_price' => 100, 86 | 'total_tax_amount' => 20, 87 | ], 88 | ], 89 | ]; 90 | 91 | $response = $gateway->authorize($data)->send()->getData(); 92 | ``` 93 | This will return the order details as well as the checkout HTML snippet to render on your site. 94 | 95 | [API documentation](https://github.com/MyOnlineStore/omnipay-klarna-checkout/blob/master/src/Message/AuthorizeRequest.php) 96 | 97 | ## Render Iframe 98 | 99 | Klarna Checkout requires an iframe to be rendered when authorizing payments: 100 | 101 | ```php 102 | $response = $gateway->fetchTransaction(['transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab']) 103 | ->send(); 104 | 105 | echo $response->getData()['checkout']['html_snippet']; 106 | ``` 107 | 108 | After submitting the form within the iframe, 109 | Klarna will redirect the client to the provided `confirmation_url` (success) 110 | or `checkout_url` (failure)`. 111 | 112 | ### Update transaction 113 | 114 | While an order has not been authorized (completed) by the client, it may be updated: 115 | 116 | ```php 117 | $response = $gateway->updateTransaction([ 118 | 'transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab', 119 | 'amount' => 200, 120 | 'tax_amount' => 40, 121 | 'currency' => 'SEK', 122 | 'locale' => 'SE', 123 | 'purchase_country' => 'SE', 124 | 'items' => [/*...*/], 125 | ])->send(); 126 | ``` 127 | 128 | The response will contain the updated order data. 129 | 130 | [API documentation](https://developers.klarna.com/api/#checkout-api-update-an-order) 131 | 132 | ### Extend authorization 133 | 134 | Klarna order authorization is valid until a specific date, and may be extended (up to a maximum of 180 days). 135 | The updated expiration date may then be retrieved with a [fetch](#fetch) request 136 | 137 | ```php 138 | if ($gateway->extendAuthorization(['transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab'])->send()->isSuccessful()) { 139 | $expiration = new \DateTimeImmutable( 140 | $gateway->fetchTransaction(['transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab']) 141 | ->send() 142 | ->getData()['management']['expires_at'] 143 | ); 144 | } 145 | ``` 146 | 147 | [API documentation](https://developers.klarna.com/api/#order-management-api-extend-authorization-time) 148 | 149 | ### Capture 150 | 151 | ```php 152 | $success = $gateway->capture([ 153 | 'transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab', 154 | 'amount' => '995', 155 | ])->send() 156 | ->isSuccessful(); 157 | ``` 158 | 159 | [API documentation](https://developers.klarna.com/api/#order-management-api-create-capture) 160 | 161 | ### Fetch 162 | 163 | A Klarna order is initially available through the checkout API. After it has been authorized, it will be available 164 | through the Order management API (and will, after some time, no longer be available in the checkout API). 165 | This fetch request will first check whether the order exitst in the checkout API. 166 | If that is not the case, or the status indicates the order is finished, 167 | it will also fetch the order from the order management API 168 | 169 | ```php 170 | $response = $gateway->fetchTransaction(['transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab']) 171 | ->send(); 172 | 173 | $success = $response->isSuccessful(); 174 | $checkoutData = $response->getData()['checkout'] ?? []; 175 | $managementData = $response->getData()['management'] ?? []; 176 | ``` 177 | API documentation | 178 | [Checkout](https://developers.klarna.com/api/#checkout-api-retrieve-an-order) | 179 | [Order management](https://developers.klarna.com/api/#order-management-api-get-order) 180 | 181 | ### Acknowlegde 182 | 183 | Acknowledge a completed order 184 | 185 | ```php 186 | $success = $gateway->acknowledge(['transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab']) 187 | ->send() 188 | ->isSuccessful(); 189 | ``` 190 | 191 | [API documentation](https://developers.klarna.com/api/#order-management-api-acknowledge-order) 192 | 193 | ### Refund 194 | 195 | ```php 196 | $success = $gateway->refund([ 197 | 'transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab', 198 | 'amount' => '995', 199 | ])->send() 200 | ->isSuccessful(); 201 | ``` 202 | 203 | [API documentation](https://developers.klarna.com/api/#order-management-api-create-a-refund) 204 | 205 | ### Void 206 | 207 | You may release the remaining authorized amount. Specifying a specific amount is not possible. 208 | 209 | ```php 210 | $success = $gateway->void(['transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab']) 211 | ->send() 212 | ->isSuccessful(); 213 | ``` 214 | 215 | [API documentation](https://developers.klarna.com/api/#order-management-api-release-remaining-authorization) 216 | 217 | ### Update customer address 218 | 219 | This may be used when updating customer address details *after* the order has been authorized. 220 | Success op this operation is subject to a risk assessment by Klarna. Both addresses are required parameters. 221 | 222 | ```php 223 | $success = $gateway->refund([ 224 | 'transactionReference' => 'a5bec272-d68d-4df9-9fdd-8e35e51f92ab', 225 | 'shipping_address' => [ 226 | 'given_name'=> 'Klara', 227 | 'family_name'=> 'Joyce', 228 | 'title'=> 'Mrs', 229 | 'street_address'=> 'Apartment 10', 230 | 'street_address2'=> '1 Safeway', 231 | 'postal_code'=> '12345', 232 | 'city'=> 'Knoxville', 233 | 'region'=> 'TN', 234 | 'country'=> 'us', 235 | 'email'=> 'klara.joyce@klarna.com', 236 | 'phone'=> '1-555-555-5555' 237 | ], 238 | 'billing_address' => [ 239 | 'given_name'=> 'Klara', 240 | 'family_name'=> 'Joyce', 241 | 'title'=> 'Mrs', 242 | 'street_address'=> 'Apartment 10', 243 | 'street_address2'=> '1 Safeway', 244 | 'postal_code'=> '12345', 245 | 'city'=> 'Knoxville', 246 | 'region'=> 'TN', 247 | 'country'=> 'us', 248 | 'email'=> 'klara.joyce@klarna.com', 249 | 'phone'=> '1-555-555-5555' 250 | ], 251 | ])->send() 252 | ->isSuccessful(); 253 | ``` 254 | 255 | [API documentation](https://developers.klarna.com/api/#order-management-api-update-customer-addresses) 256 | 257 | ### Update merchant reference(s) 258 | 259 | If an order has been authorized by the client, its merchant references may be updated: 260 | 261 | ```php 262 | $response = $gateway->updateMerchantReferences([ 263 | 'merchant_reference1' => 'foo', 264 | 'merchant_reference2' => 'bar', 265 | ])->send(); 266 | ``` 267 | 268 | [API documentation](https://developers.klarna.com/api/#order-management-api-update-merchant-references) 269 | 270 | 271 | ## Units 272 | 273 | Klarna expresses amounts in minor units as described [here](https://developers.klarna.com/api/#data-types). 274 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myonlinestore/omnipay-klarna-checkout", 3 | "type": "library", 4 | "description": "Klarna Checkout gateway for Omnipay payment processing library", 5 | "keywords": ["Klarna Checkout", "Omnipay"], 6 | "homepage": "https://github.com/MyOnlineStore/omnipay-klarna-checkout", 7 | "license": "MIT", 8 | "autoload": { 9 | "psr-4" : { 10 | "MyOnlineStore\\Omnipay\\KlarnaCheckout\\" : "src/", 11 | "MyOnlineStore\\Tests\\Omnipay\\KlarnaCheckout\\" : "tests/" 12 | } 13 | }, 14 | "require": { 15 | "php": "^7.4 | ^8.0", 16 | "omnipay/common": "^3.1", 17 | "ext-json": "*" 18 | }, 19 | "require-dev": { 20 | "myonlinestore/coding-standard": "^3.1", 21 | "myonlinestore/php-devtools": "^0.2", 22 | "omnipay/tests": "^4.1", 23 | "vimeo/psalm": "^4.18" 24 | }, 25 | "config": { 26 | "sort-packages": true, 27 | "allow-plugins": { 28 | "dealerdirect/phpcodesniffer-composer-installer": true, 29 | "composer/package-versions-deprecated": true 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | src 13 | tests 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | src 10 | 11 | 12 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /psalm-baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $request->getSecret() 6 | $request->getUsername() 7 | 8 | 9 | 10 | 11 | $this 12 | 13 | 14 | authorize 15 | capture 16 | refund 17 | setBaseUrl 18 | void 19 | 20 | 21 | self 22 | 23 | 24 | 25 | 26 | setMerchantData 27 | setTaxRate 28 | setTotalAmount 29 | setTotalDiscountAmount 30 | setTotalTaxAmount 31 | setType 32 | 33 | 34 | 35 | 36 | ItemInterface[] 37 | 38 | 39 | add 40 | 41 | 42 | $item 43 | 44 | 45 | 46 | 47 | getAmount 48 | 49 | 50 | 51 | 52 | Money|null 53 | 54 | 55 | setLocale 56 | setTaxAmount 57 | 58 | 59 | $items 60 | 61 | 62 | 63 | 64 | AcknowledgeRequest 65 | 66 | 67 | 68 | 69 | $response->getBody() 70 | 71 | 72 | AuthorizeRequest 73 | 74 | 75 | 76 | 77 | getRedirectData 78 | getRedirectUrl 79 | 80 | 81 | $data 82 | $renderUrl 83 | 84 | 85 | $this->renderUrl 86 | null 87 | 88 | 89 | 90 | 91 | CaptureRequest 92 | 93 | 94 | 95 | 96 | ExtendAuthorizationRequest 97 | 98 | 99 | 100 | 101 | $responseData 102 | 103 | 104 | FetchTransactionRequest 105 | 106 | 107 | 108 | 109 | RefundRequest 110 | 111 | 112 | 113 | 114 | getArrayCopy 115 | getArrayCopy 116 | 117 | 118 | UpdateCustomerAddressRequest 119 | 120 | 121 | 122 | 123 | (int) $statusCode 124 | 125 | 126 | 127 | 128 | UpdateMerchantReferencesRequest 129 | 130 | 131 | 132 | 133 | UpdateTransactionRequest 134 | 135 | 136 | 137 | 138 | VoidRequest 139 | 140 | 141 | 142 | 143 | public function initialize(array $parameters = []) 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /psalm.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Address.php: -------------------------------------------------------------------------------- 1 | null, 17 | 'reference' => null, 18 | 'attention' => null, 19 | 'family_name' => null, 20 | 'given_name' => null, 21 | 'email' => null, 22 | 'title' => null, 23 | 'street_address' => null, 24 | 'street_address2' => null, 25 | 'street_name' => null, 26 | 'house_extension' => null, 27 | 'street_number' => null, 28 | 'postal_code' => null, 29 | 'city' => null, 30 | 'region' => null, 31 | 'phone' => null, 32 | 'country' => null, 33 | ]; 34 | 35 | return new self(\array_merge($defaults, \array_intersect_key($data, $defaults))); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AuthenticationRequestHeaderProvider.php: -------------------------------------------------------------------------------- 1 | \sprintf( 14 | 'Basic %s', 15 | \base64_encode( 16 | \sprintf( 17 | '%s:%s', 18 | $request->getUsername(), 19 | $request->getSecret() 20 | ) 21 | ) 22 | ), 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/CurrencyAwareTrait.php: -------------------------------------------------------------------------------- 1 | getCurrency()); 29 | } catch (\InvalidArgumentException $exception) { 30 | $currency = new Currency('USD'); 31 | } 32 | 33 | $moneyParser = new DecimalMoneyParser(new ISOCurrencies()); 34 | 35 | return $moneyParser->parse((string) $amount, $currency); 36 | } 37 | 38 | /** 39 | * @param Money $money 40 | * 41 | * @return int 42 | * 43 | * @throws ParserException 44 | */ 45 | protected function toCurrencyMinorUnits(Money $money): int 46 | { 47 | $moneyParser = new DecimalMoneyParser(new ISOCurrencies()); 48 | 49 | return (int) $moneyParser->parse($money->getAmount(), $money->getCurrency())->getAmount(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Customer.php: -------------------------------------------------------------------------------- 1 | null, 17 | 'type' => 'person', 18 | ]; 19 | 20 | return new self(\array_merge($defaults, \array_intersect_key($data, $defaults))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Gateway.php: -------------------------------------------------------------------------------- 1 | createRequest(AcknowledgeRequest::class, $options); 35 | } 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | public function authorize(array $options = []) 41 | { 42 | return $this->createRequest(AuthorizeRequest::class, $options); 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function capture(array $options = []) 49 | { 50 | return $this->createRequest(CaptureRequest::class, $options); 51 | } 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public function extendAuthorization(array $options = []): RequestInterface 57 | { 58 | return $this->createRequest(ExtendAuthorizationRequest::class, $options); 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public function fetchTransaction(array $options = []): RequestInterface 65 | { 66 | return $this->createRequest(FetchTransactionRequest::class, $options); 67 | } 68 | 69 | /** 70 | * @return string REGION_* constant value 71 | */ 72 | public function getApiRegion(): string 73 | { 74 | return $this->getParameter('api_region'); 75 | } 76 | 77 | /** 78 | * @inheritDoc 79 | */ 80 | public function getDefaultParameters(): array 81 | { 82 | return [ 83 | 'api_region' => self::API_VERSION_EUROPE, 84 | 'secret' => '', 85 | 'testMode' => true, 86 | 'username' => '', 87 | ]; 88 | } 89 | 90 | public function getName(): string 91 | { 92 | return 'KlarnaCheckout'; 93 | } 94 | 95 | public function getSecret(): string 96 | { 97 | return $this->getParameter('secret'); 98 | } 99 | 100 | public function getUsername(): string 101 | { 102 | return $this->getParameter('username'); 103 | } 104 | 105 | /** 106 | * @inheritDoc 107 | */ 108 | public function initialize(array $parameters = []) 109 | { 110 | parent::initialize($parameters); 111 | 112 | $this->setBaseUrl(); 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * @inheritdoc 119 | */ 120 | public function refund(array $options = []) 121 | { 122 | return $this->createRequest(RefundRequest::class, $options); 123 | } 124 | 125 | /** 126 | * @param string $region 127 | * 128 | * @return $this 129 | */ 130 | public function setApiRegion(string $region): self 131 | { 132 | $this->setParameter('api_region', $region); 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * @param string $secret 139 | * 140 | * @return $this 141 | */ 142 | public function setSecret(string $secret): self 143 | { 144 | $this->setParameter('secret', $secret); 145 | 146 | return $this; 147 | } 148 | 149 | /** 150 | * @param string $username 151 | * 152 | * @return $this 153 | */ 154 | public function setUsername(string $username): self 155 | { 156 | $this->setParameter('username', $username); 157 | 158 | return $this; 159 | } 160 | 161 | public function setTestMode($value): self 162 | { 163 | parent::setTestMode($value); 164 | 165 | $this->setBaseUrl(); 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * @inheritDoc 172 | */ 173 | public function updateCustomerAddress(array $options = []): RequestInterface 174 | { 175 | return $this->createRequest(UpdateCustomerAddressRequest::class, $options); 176 | } 177 | 178 | public function updateMerchantReferences(array $options = []): RequestInterface 179 | { 180 | return $this->createRequest(UpdateMerchantReferencesRequest::class, $options); 181 | } 182 | 183 | /** 184 | * @inheritdoc 185 | */ 186 | public function updateTransaction(array $options = []): RequestInterface 187 | { 188 | return $this->createRequest(UpdateTransactionRequest::class, $options); 189 | } 190 | 191 | /** 192 | * @inheritdoc 193 | */ 194 | public function void(array $options = []) 195 | { 196 | return $this->createRequest(VoidRequest::class, $options); 197 | } 198 | 199 | private function setBaseUrl() 200 | { 201 | if (self::API_VERSION_EUROPE === $this->getApiRegion()) { 202 | $this->parameters->set('base_url', $this->getTestMode() ? self::EU_TEST_BASE_URL : self::EU_BASE_URL); 203 | 204 | return; 205 | } 206 | 207 | $this->parameters->set('base_url', $this->getTestMode() ? self::NA_TEST_BASE_URL : self::NA_BASE_URL); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/GatewayInterface.php: -------------------------------------------------------------------------------- 1 | getParameter('merchant_data'); 14 | } 15 | 16 | /** 17 | * @inheritDoc 18 | */ 19 | public function getTaxRate() 20 | { 21 | return $this->getParameter('tax_rate'); 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | public function getTotalAmount() 28 | { 29 | return $this->getParameter('total_amount'); 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function getTotalDiscountAmount() 36 | { 37 | return $this->getParameter('total_discount_amount'); 38 | } 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | public function getTotalTaxAmount() 44 | { 45 | return $this->getParameter('total_tax_amount'); 46 | } 47 | 48 | /** 49 | * @inheritDoc 50 | */ 51 | public function getType() 52 | { 53 | return $this->getParameter('type'); 54 | } 55 | 56 | /** 57 | * @param string $data 58 | */ 59 | public function setMerchantData($data) 60 | { 61 | $this->setParameter('merchant_data', $data); 62 | } 63 | 64 | /** 65 | * @param int $taxRate 66 | */ 67 | public function setTaxRate($taxRate) 68 | { 69 | $this->setParameter('tax_rate', $taxRate); 70 | } 71 | 72 | /** 73 | * @param int $amount 74 | */ 75 | public function setTotalAmount($amount) 76 | { 77 | $this->setParameter('total_amount', $amount); 78 | } 79 | 80 | /** 81 | * @param int $amount 82 | */ 83 | public function setTotalDiscountAmount($amount) 84 | { 85 | $this->setParameter('total_discount_amount', $amount); 86 | } 87 | 88 | /** 89 | * @param int $amount 90 | */ 91 | public function setTotalTaxAmount($amount) 92 | { 93 | $this->setParameter('total_tax_amount', $amount); 94 | } 95 | 96 | /** 97 | * @param string $type 98 | */ 99 | public function setType($type) 100 | { 101 | $this->setParameter('type', $type); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/ItemBag.php: -------------------------------------------------------------------------------- 1 | items[] = $item; 19 | } else { 20 | $this->items[] = new Item($item); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ItemInterface.php: -------------------------------------------------------------------------------- 1 | getParameter('billing_address'); 21 | } 22 | 23 | /** 24 | * @return Customer|null 25 | */ 26 | public function getCustomer() 27 | { 28 | return $this->getParameter('customer'); 29 | } 30 | 31 | /** 32 | * @return bool|null 33 | */ 34 | public function getGuiAutofocus() 35 | { 36 | return $this->getParameter('gui_autofocus'); 37 | } 38 | 39 | /** 40 | * @return bool|null 41 | */ 42 | public function getGuiMinimalConfirmation() 43 | { 44 | return $this->getParameter('gui_minimal_confirmation'); 45 | } 46 | 47 | /** 48 | * @return string|null 49 | */ 50 | public function getPurchaseCountry() 51 | { 52 | return $this->getParameter('purchase_country'); 53 | } 54 | 55 | /** 56 | * @return Address|null 57 | */ 58 | public function getShippingAddress() 59 | { 60 | return $this->getParameter('shipping_address'); 61 | } 62 | 63 | /** 64 | * @return string[]|null ISO 3166 alpha-2 codes of shipping countries, or null if none are specified 65 | */ 66 | public function getShippingCountries() 67 | { 68 | return $this->getParameter('shipping_countries'); 69 | } 70 | 71 | /** 72 | * @return WidgetOptions|null 73 | */ 74 | public function getWidgetOptions() 75 | { 76 | return $this->getParameter('widget_options'); 77 | } 78 | 79 | /** 80 | * @param array $billingAddress 81 | * 82 | * @return $this 83 | */ 84 | public function setBillingAddress($billingAddress): self 85 | { 86 | $this->setParameter('billing_address', Address::fromArray($billingAddress)); 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * @param array $customer 93 | * 94 | * @return $this 95 | */ 96 | public function setCustomer(array $customer): self 97 | { 98 | $this->setParameter('customer', Customer::fromArray($customer)); 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param bool $value 105 | * 106 | * @return $this 107 | */ 108 | public function setGuiAutofocus(bool $value): self 109 | { 110 | $this->setParameter('gui_autofocus', $value); 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * @param bool $value 117 | * 118 | * @return $this 119 | */ 120 | public function setGuiMinimalConfirmation(bool $value): self 121 | { 122 | $this->setParameter('gui_minimal_confirmation', $value); 123 | 124 | return $this; 125 | } 126 | 127 | /** 128 | * @param string $value 129 | * 130 | * @return $this 131 | */ 132 | public function setPurchaseCountry(string $value): self 133 | { 134 | $this->setParameter('purchase_country', $value); 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * @param array $shippingAddress 141 | * 142 | * @return $this 143 | */ 144 | public function setShippingAddress(array $shippingAddress): self 145 | { 146 | $this->setParameter('shipping_address', Address::fromArray($shippingAddress)); 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * @param string[] $countries ISO 3166 alpha-2 codes of shipping countries 153 | * 154 | * @return $this 155 | */ 156 | public function setShippingCountries(array $countries): self 157 | { 158 | $this->setParameter('shipping_countries', $countries); 159 | 160 | return $this; 161 | } 162 | 163 | /** 164 | * @param array $widgetOptions 165 | * 166 | * @return $this 167 | */ 168 | public function setWidgetOptions(array $widgetOptions): self 169 | { 170 | $this->setParameter('widget_options', WidgetOptions::fromArray($widgetOptions)); 171 | 172 | return $this; 173 | } 174 | 175 | /** 176 | * @return array 177 | */ 178 | protected function getOrderData(): array 179 | { 180 | $data = [ 181 | 'order_amount' => $this->getAmountInteger(), 182 | 'order_tax_amount' => null === $this->getTaxAmount() ? 0 : (int) $this->getTaxAmount()->getAmount(), 183 | 'order_lines' => $this->getItemData($this->getItems() ?? new ItemBag()), 184 | 'purchase_currency' => $this->getCurrency(), 185 | 'purchase_country' => $this->getPurchaseCountry(), 186 | ]; 187 | 188 | if (null !== $locale = $this->getLocale()) { 189 | $data['locale'] = \str_replace('_', '-', $locale); 190 | } 191 | 192 | if (null !== $shippingCountries = $this->getShippingCountries()) { 193 | $data['shipping_countries'] = $shippingCountries; 194 | } 195 | 196 | if (null !== $shippingAddress = $this->getShippingAddress()) { 197 | $data['shipping_address'] = $shippingAddress->getArrayCopy(); 198 | } 199 | 200 | if (null !== $billingAddress = $this->getBillingAddress()) { 201 | $data['billing_address'] = $billingAddress->getArrayCopy(); 202 | } 203 | 204 | if (null !== $merchantReference1 = $this->getMerchantReference1()) { 205 | $data['merchant_reference1'] = $merchantReference1; 206 | } 207 | 208 | if (null !== $merchantReference2 = $this->getMerchantReference2()) { 209 | $data['merchant_reference2'] = $merchantReference2; 210 | } 211 | 212 | if (null !== $widgetOptions = $this->getWidgetOptions()) { 213 | $data['options'] = $widgetOptions->getArrayCopy(); 214 | } 215 | 216 | if (null !== $customer = $this->getCustomer()) { 217 | $data['customer'] = $customer->getArrayCopy(); 218 | } 219 | 220 | $guiOptions = []; 221 | 222 | if (false === $this->getGuiAutofocus()) { 223 | $guiOptions[] = 'disable_autofocus'; 224 | } 225 | 226 | if ($this->getGuiMinimalConfirmation()) { 227 | $guiOptions[] = 'minimal_confirmation'; 228 | } 229 | 230 | if (!empty($guiOptions)) { 231 | $data['gui'] = ['options' => $guiOptions]; 232 | } 233 | 234 | return $data; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/Message/AbstractRequest.php: -------------------------------------------------------------------------------- 1 | getParameter('amount')) { 28 | return null; 29 | } 30 | 31 | return $this->convertToMoney($amount); 32 | } 33 | 34 | /** 35 | * @return string|null REGION_* constant value 36 | */ 37 | public function getApiRegion() 38 | { 39 | return $this->getParameter('api_region'); 40 | } 41 | 42 | /** 43 | * @return string|null 44 | */ 45 | public function getBaseUrl() 46 | { 47 | return $this->getParameter('base_url'); 48 | } 49 | 50 | /** 51 | * RFC 1766 customer's locale. 52 | * 53 | * @return string|null 54 | */ 55 | public function getLocale() 56 | { 57 | return $this->getParameter('locale'); 58 | } 59 | 60 | /** 61 | * @return string|null 62 | */ 63 | public function getMerchantReference1() 64 | { 65 | return $this->getParameter('merchant_reference1'); 66 | } 67 | 68 | /** 69 | * @return string|null 70 | */ 71 | public function getMerchantReference2() 72 | { 73 | return $this->getParameter('merchant_reference2'); 74 | } 75 | 76 | /** 77 | * @return string|null 78 | */ 79 | public function getSecret() 80 | { 81 | return $this->getParameter('secret'); 82 | } 83 | 84 | /** 85 | * The total tax amount of the order 86 | * 87 | * @return Money|null 88 | */ 89 | public function getTaxAmount() 90 | { 91 | if (null === $amount = $this->getParameter('tax_amount')) { 92 | return null; 93 | } 94 | 95 | return $this->convertToMoney($amount); 96 | } 97 | 98 | /** 99 | * @return string|null 100 | */ 101 | public function getUsername() 102 | { 103 | return $this->getParameter('username'); 104 | } 105 | 106 | /** 107 | * @param string $region 108 | * 109 | * @return $this 110 | */ 111 | public function setApiRegion(string $region): self 112 | { 113 | $this->setParameter('api_region', $region); 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * @param string $baseUrl 120 | * 121 | * @return $this 122 | */ 123 | public function setBaseUrl(string $baseUrl): self 124 | { 125 | $this->setParameter('base_url', $baseUrl); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * @inheritdoc 132 | */ 133 | public function setItems($items) 134 | { 135 | if ($items && !$items instanceof ItemBag) { 136 | $items = new ItemBag($items); 137 | } 138 | 139 | return $this->setParameter('items', $items); 140 | } 141 | 142 | public function setLocale(string $locale) 143 | { 144 | $this->setParameter('locale', $locale); 145 | } 146 | 147 | /** 148 | * @param string $merchantReference 149 | * 150 | * @return $this 151 | */ 152 | public function setMerchantReference1(string $merchantReference): self 153 | { 154 | $this->setParameter('merchant_reference1', $merchantReference); 155 | 156 | return $this; 157 | } 158 | 159 | /** 160 | * @param string $merchantReference 161 | * 162 | * @return $this 163 | */ 164 | public function setMerchantReference2(string $merchantReference): self 165 | { 166 | $this->setParameter('merchant_reference2', $merchantReference); 167 | 168 | return $this; 169 | } 170 | 171 | /** 172 | * @param string $secret 173 | * 174 | * @return $this 175 | */ 176 | public function setSecret(string $secret): self 177 | { 178 | $this->setParameter('secret', $secret); 179 | 180 | return $this; 181 | } 182 | 183 | /** 184 | * @param mixed $value 185 | */ 186 | public function setTaxAmount($value) 187 | { 188 | $this->setParameter('tax_amount', $value); 189 | } 190 | 191 | /** 192 | * @param string $username 193 | * 194 | * @return $this 195 | */ 196 | public function setUsername(string $username): self 197 | { 198 | $this->setParameter('username', $username); 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * @param ResponseInterface $response 205 | * 206 | * @return array 207 | */ 208 | protected function getResponseBody(ResponseInterface $response): array 209 | { 210 | try { 211 | return \json_decode($response->getBody()->getContents(), true); 212 | } catch (\TypeError $exception) { 213 | return []; 214 | } 215 | } 216 | 217 | /** 218 | * @param string $method 219 | * @param string $url 220 | * @param mixed $data 221 | * 222 | * @return ResponseInterface 223 | * 224 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 225 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 226 | */ 227 | protected function sendRequest(string $method, string $url, $data): ResponseInterface 228 | { 229 | $headers = (new AuthenticationRequestHeaderProvider())->getHeaders($this); 230 | 231 | if ('GET' === $method) { 232 | return $this->httpClient->request( 233 | $method, 234 | $this->getBaseUrl() . $url, 235 | $headers 236 | ); 237 | } 238 | 239 | return $this->httpClient->request( 240 | $method, 241 | $this->getBaseUrl() . $url, 242 | \array_merge( 243 | ['Content-Type' => 'application/json'], 244 | $headers 245 | ), 246 | \json_encode($data) 247 | ); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/Message/AbstractResponse.php: -------------------------------------------------------------------------------- 1 | data['error_code'] ?? null; 14 | } 15 | 16 | /** 17 | * @inheritdoc 18 | */ 19 | public function getMessage() 20 | { 21 | return $this->data['error_message'] ?? null; 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | public function getTransactionReference() 28 | { 29 | return $this->data['order_id'] ?? null; 30 | } 31 | 32 | public function isSuccessful(): bool 33 | { 34 | return !isset($this->data['error_code']); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Message/AcknowledgeRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 20 | 21 | return null; 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | * 27 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 28 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 29 | */ 30 | public function sendData($data) 31 | { 32 | $response = $this->sendRequest( 33 | 'POST', 34 | \sprintf( 35 | '/ordermanagement/v1/orders/%s/acknowledge', 36 | $this->getTransactionReference() 37 | ), 38 | $data 39 | ); 40 | 41 | return new AcknowledgeResponse( 42 | $this, 43 | $this->getResponseBody($response), 44 | $response->getStatusCode() 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Message/AcknowledgeResponse.php: -------------------------------------------------------------------------------- 1 | statusCode = $statusCode; 23 | } 24 | 25 | public function getStatusCode(): int 26 | { 27 | return $this->statusCode; 28 | } 29 | 30 | public function isSuccessful(): bool 31 | { 32 | return parent::isSuccessful() && 204 === $this->getStatusCode(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Message/AuthorizeRequest.php: -------------------------------------------------------------------------------- 1 | validate( 26 | 'amount', 27 | 'currency', 28 | 'items', 29 | 'locale', 30 | 'purchase_country', 31 | 'tax_amount' 32 | ); 33 | 34 | $data = $this->getOrderData(); 35 | $data['merchant_urls'] = $this->getMerchantUrls(); 36 | 37 | return $data; 38 | } 39 | 40 | /** 41 | * @return string|null 42 | */ 43 | public function getRenderUrl() 44 | { 45 | return $this->getParameter('render_url'); 46 | } 47 | 48 | /** 49 | * @inheritDoc 50 | * 51 | * @throws InvalidResponseException 52 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 53 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 54 | */ 55 | public function sendData($data) 56 | { 57 | $response = $this->getTransactionReference() ? 58 | $this->sendRequest('GET', '/checkout/v3/orders/' . $this->getTransactionReference(), $data) : 59 | $this->sendRequest('POST', '/checkout/v3/orders', $data); 60 | 61 | if ($response->getStatusCode() >= 400) { 62 | throw new InvalidResponseException( 63 | \sprintf('Reason: %s (%s)', $response->getReasonPhrase(), $response->getBody()) 64 | ); 65 | } 66 | 67 | return new AuthorizeResponse($this, $this->getResponseBody($response), $this->getRenderUrl()); 68 | } 69 | 70 | /** 71 | * @param string $url 72 | * 73 | * @return $this 74 | */ 75 | public function setRenderUrl(string $url): self 76 | { 77 | $this->setParameter('render_url', $url); 78 | 79 | return $this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Message/AuthorizeResponse.php: -------------------------------------------------------------------------------- 1 | renderUrl = $renderUrl; 21 | } 22 | 23 | /** 24 | * @inheritDoc 25 | */ 26 | public function getRedirectData() 27 | { 28 | return null; 29 | } 30 | 31 | /** 32 | * @inheritDoc 33 | */ 34 | public function getRedirectUrl() 35 | { 36 | return $this->renderUrl; 37 | } 38 | 39 | public function isRedirect(): bool 40 | { 41 | return null !== $this->renderUrl; 42 | } 43 | 44 | public function isSuccessful(): bool 45 | { 46 | // Authorize is only successful once it has been acknowledged 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Message/CaptureRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference', 'amount'); 22 | 23 | $data = ['captured_amount' => $this->getAmountInteger()]; 24 | 25 | if (null !== $items = $this->getItems()) { 26 | $data['order_lines'] = $this->getItemData($items); 27 | } 28 | 29 | return $data; 30 | } 31 | 32 | /** 33 | * @inheritdoc 34 | * 35 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 36 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 37 | */ 38 | public function sendData($data) 39 | { 40 | $response = $this->sendRequest( 41 | 'POST', 42 | \sprintf( 43 | '/ordermanagement/v1/orders/%s/captures', 44 | $this->getTransactionReference() 45 | ), 46 | $data 47 | ); 48 | 49 | return new CaptureResponse( 50 | $this, 51 | $this->getResponseBody($response), 52 | $this->getTransactionReference(), 53 | $response->getStatusCode() 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Message/CaptureResponse.php: -------------------------------------------------------------------------------- 1 | transactionReference = $transactionReference; 27 | $this->statusCode = $statusCode; 28 | } 29 | 30 | public function getStatusCode(): int 31 | { 32 | return $this->statusCode; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function getTransactionReference() 39 | { 40 | return $this->transactionReference; 41 | } 42 | 43 | public function isSuccessful(): bool 44 | { 45 | return parent::isSuccessful() && 201 === $this->statusCode; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Message/ExtendAuthorizationRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 20 | 21 | return null; 22 | } 23 | 24 | /** 25 | * @param mixed $data 26 | * 27 | * @return ExtendAuthorizationResponse 28 | * 29 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 30 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 31 | */ 32 | public function sendData($data): ExtendAuthorizationResponse 33 | { 34 | $responseBody = $this->getResponseBody( 35 | $this->sendRequest( 36 | 'POST', 37 | \sprintf('/ordermanagement/v1/orders/%s/extend-authorization-time', $this->getTransactionReference()), 38 | $data 39 | ) 40 | ); 41 | 42 | return new ExtendAuthorizationResponse( 43 | $this, 44 | \array_merge( 45 | $responseBody, 46 | ['order_id' => $this->getTransactionReference()] 47 | ) 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Message/ExtendAuthorizationResponse.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 20 | 21 | return null; 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | * 27 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 28 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 29 | */ 30 | public function sendData($data) 31 | { 32 | $response = $this->sendRequest( 33 | 'GET', 34 | '/checkout/v3/orders/' . $this->getTransactionReference(), 35 | $data 36 | ); 37 | 38 | $responseData['checkout'] = $this->getResponseBody($response); 39 | 40 | if ( 41 | (isset($responseData['checkout']['status']) && 'checkout_complete' === $responseData['checkout']['status']) 42 | || 404 === $response->getStatusCode() 43 | ) { 44 | $responseData['management'] = $this->getResponseBody( 45 | $this->sendRequest( 46 | 'GET', 47 | '/ordermanagement/v1/orders/' . $this->getTransactionReference(), 48 | $data 49 | ) 50 | ); 51 | } 52 | 53 | return new FetchTransactionResponse($this, $responseData); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Message/FetchTransactionResponse.php: -------------------------------------------------------------------------------- 1 | data['checkout']['order_id'] ?? $this->data['management']['order_id']; 14 | } 15 | 16 | public function isSuccessful(): bool 17 | { 18 | return parent::isSuccessful() && 19 | (!empty($this->data['checkout']['status']) || !empty($this->data['management']['status'])); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Message/ItemDataTrait.php: -------------------------------------------------------------------------------- 1 | getTaxRate(); 22 | $price = null === $item->getPrice() ? $this->convertToMoney(0) : $this->convertToMoney($item->getPrice()); 23 | $totalTaxAmount = null === $item->getTotalTaxAmount() 24 | ? $this->convertToMoney(0) 25 | : $this->convertToMoney($item->getTotalTaxAmount()); 26 | $totalDiscountAmount = null === $item->getTotalDiscountAmount() 27 | ? $this->convertToMoney(0) 28 | : $this->convertToMoney($item->getTotalDiscountAmount()); 29 | $totalAmount = null === $item->getTotalAmount() 30 | ? $price->multiply($item->getQuantity())->subtract($totalDiscountAmount) 31 | : $this->convertToMoney($item->getTotalAmount()); 32 | 33 | $orderLines[] = [ 34 | 'type' => $item->getType(), 35 | 'name' => $item->getName(), 36 | 'quantity' => $item->getQuantity(), 37 | 'tax_rate' => null === $taxRate ? 0 : (int) ($item->getTaxRate() * 100), 38 | 'total_amount' => (int) $totalAmount->getAmount(), 39 | 'total_tax_amount' => (int) $totalTaxAmount->getAmount(), 40 | 'total_discount_amount' => (int) $totalDiscountAmount->getAmount(), 41 | 'unit_price' => (int) $price->getAmount(), 42 | 'merchant_data' => $item->getMerchantData(), 43 | ]; 44 | } 45 | 46 | return $orderLines; 47 | } 48 | 49 | abstract protected function convertToMoney($amount): Money; 50 | } 51 | -------------------------------------------------------------------------------- /src/Message/MerchantUrlsDataTrait.php: -------------------------------------------------------------------------------- 1 | getParameter('addressUpdateUrl'); 17 | } 18 | 19 | /** 20 | * @return string|null 21 | */ 22 | public function getCancellationTermsUrl() 23 | { 24 | return $this->getParameter('cancellationTermsUrl'); 25 | } 26 | 27 | /** 28 | * @return array 29 | * 30 | * @throws InvalidRequestException 31 | */ 32 | public function getMerchantUrls(): array 33 | { 34 | $this->validate('notifyUrl', 'returnUrl', 'termsUrl', 'validationUrl'); 35 | 36 | $returnUrl = $this->getReturnUrl(); 37 | 38 | $merchantUrls = [ 39 | 'checkout' => $returnUrl, 40 | 'confirmation' => $this->getConfirmationUrl() ?? $returnUrl, 41 | 'push' => $this->getNotifyUrl(), 42 | 'terms' => $this->getTermsUrl(), 43 | 'validation' => $this->getValidationUrl(), 44 | ]; 45 | 46 | if (null !== ($cancellationTermsUrl = $this->getCancellationTermsUrl())) { 47 | $merchantUrls['cancellation_terms'] = $cancellationTermsUrl; 48 | } 49 | 50 | if (null !== ($addressUpdateUrl = $this->getAddressUpdateUrl())) { 51 | $merchantUrls['address_update'] = $addressUpdateUrl; 52 | } 53 | 54 | return $merchantUrls; 55 | } 56 | 57 | /** 58 | * @return string|null 59 | */ 60 | abstract public function getNotifyUrl(); 61 | 62 | /** 63 | * @return string|null 64 | */ 65 | abstract public function getReturnUrl(); 66 | 67 | /** 68 | * @return string|null 69 | */ 70 | public function getTermsUrl() 71 | { 72 | return $this->getParameter('termsUrl'); 73 | } 74 | 75 | /** 76 | * @return string|null 77 | */ 78 | public function getValidationUrl() 79 | { 80 | return $this->getParameter('validationUrl'); 81 | } 82 | 83 | /** 84 | * @return string|null 85 | */ 86 | public function getConfirmationUrl() 87 | { 88 | return $this->getParameter('confirmationUrl'); 89 | } 90 | 91 | /** 92 | * @param string $url 93 | * 94 | * @return $this 95 | */ 96 | public function setAddressUpdateUrl(string $url): self 97 | { 98 | $this->setParameter('addressUpdateUrl', $url); 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param string $url 105 | * 106 | * @return $this 107 | */ 108 | public function setCancellationTermsUrl(string $url): self 109 | { 110 | $this->setParameter('cancellationTermsUrl', $url); 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * @param string $url 117 | * 118 | * @return $this 119 | */ 120 | public function setTermsUrl(string $url): self 121 | { 122 | $this->setParameter('termsUrl', $url); 123 | 124 | return $this; 125 | } 126 | 127 | /** 128 | * @param string $url 129 | * 130 | * @return $this 131 | */ 132 | public function setValidationUrl(string $url): self 133 | { 134 | $this->setParameter('validationUrl', $url); 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * @param string $url 141 | * 142 | * @return $this 143 | */ 144 | public function setConfirmationUrl(string $url): self 145 | { 146 | $this->setParameter('confirmationUrl', $url); 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * @param string $args,... a variable length list of required parameters 153 | * 154 | * @throws InvalidRequestException 155 | */ 156 | abstract public function validate(...$args); 157 | 158 | /** 159 | * @param string $key 160 | * 161 | * @return mixed 162 | */ 163 | abstract protected function getParameter($key); 164 | 165 | /** 166 | * Set a single parameter 167 | * 168 | * @param string $key The parameter key 169 | * @param mixed $value The value to set 170 | * 171 | * @return AbstractRequest Provides a fluent interface 172 | * 173 | * @throws RuntimeException if a request parameter is modified after the request has been sent. 174 | */ 175 | abstract protected function setParameter($key, $value); 176 | } 177 | -------------------------------------------------------------------------------- /src/Message/RefundRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference', 'amount'); 24 | 25 | $data = ['refunded_amount' => $this->getAmountInteger()]; 26 | $items = $this->getItems(); 27 | 28 | if (null !== $items) { 29 | $data['order_lines'] = $this->getItemData($items); 30 | } 31 | 32 | return $data; 33 | } 34 | 35 | /** 36 | * @inheritdoc 37 | * 38 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 39 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 40 | */ 41 | public function sendData($data) 42 | { 43 | $response = $this->sendRequest( 44 | 'POST', 45 | \sprintf('/ordermanagement/v1/orders/%s/refunds', $this->getTransactionReference()), 46 | $data 47 | ); 48 | 49 | return new RefundResponse( 50 | $this, 51 | $this->getResponseBody($response), 52 | $response->getStatusCode() 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Message/RefundResponse.php: -------------------------------------------------------------------------------- 1 | statusCode = $statusCode; 23 | } 24 | 25 | public function getStatusCode(): int 26 | { 27 | return $this->statusCode; 28 | } 29 | 30 | public function isSuccessful(): bool 31 | { 32 | return parent::isSuccessful() && 201 === $this->statusCode; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Message/UpdateCustomerAddressRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference', 'billing_address', 'shipping_address'); 20 | 21 | return [ 22 | 'shipping_address' => $this->getShippingAddress()->getArrayCopy(), 23 | 'billing_address' => $this->getBillingAddress()->getArrayCopy(), 24 | ]; 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | * 30 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 31 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 32 | */ 33 | public function sendData($data) 34 | { 35 | $response = $this->sendRequest( 36 | 'PATCH', 37 | \sprintf('/ordermanagement/v1/orders/%s/customer-details', $this->getTransactionReference()), 38 | $data 39 | ); 40 | 41 | return new UpdateCustomerAddressResponse( 42 | $this, 43 | \array_merge( 44 | $this->getResponseBody($response), 45 | ['order_id' => $this->getTransactionReference()] 46 | ), 47 | $response->getStatusCode() 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Message/UpdateCustomerAddressResponse.php: -------------------------------------------------------------------------------- 1 | statusCode = (int) $statusCode; 23 | } 24 | 25 | public function isSuccessful(): bool 26 | { 27 | return parent::isSuccessful() && 204 === $this->statusCode; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Message/UpdateMerchantReferencesRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 11 | 12 | return [ 13 | 'merchant_reference1' => $this->getMerchantReference1(), 14 | 'merchant_reference2' => $this->getMerchantReference2(), 15 | ]; 16 | } 17 | 18 | public function sendData($data) 19 | { 20 | return new UpdateMerchantReferencesResponse( 21 | $this, 22 | $this->getResponseBody( 23 | $this->sendRequest( 24 | 'PATCH', 25 | \sprintf('/ordermanagement/v1/orders/%s/merchant-references', $this->getTransactionReference()), 26 | $data 27 | ) 28 | ) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Message/UpdateMerchantReferencesResponse.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 24 | $data = $this->getOrderData(); 25 | 26 | try { 27 | $data['merchant_urls'] = $this->getMerchantUrls(); 28 | } catch (InvalidRequestException $exception) { 29 | // Insufficient data for merchant urls 30 | } 31 | 32 | return $data; 33 | } 34 | 35 | /** 36 | * @inheritdoc 37 | * 38 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 39 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 40 | */ 41 | public function sendData($data) 42 | { 43 | return new UpdateTransactionResponse( 44 | $this, 45 | $this->getResponseBody( 46 | $this->sendRequest( 47 | 'POST', 48 | \sprintf('/checkout/v3/orders/%s', $this->getTransactionReference()), 49 | $data 50 | ) 51 | ) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Message/UpdateTransactionResponse.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 20 | 21 | return []; 22 | } 23 | 24 | /** 25 | * @inheritdoc 26 | * 27 | * @throws RequestException when the HTTP client is passed a request that is invalid and cannot be sent. 28 | * @throws NetworkException if there is an error with the network or the remote server cannot be reached. 29 | */ 30 | public function sendData($data) 31 | { 32 | $baseUrl = \sprintf('/ordermanagement/v1/orders/%s', $this->getTransactionReference()); 33 | $orderManagementResponse = $this->sendRequest('GET', $baseUrl, []); 34 | 35 | $order = $this->getResponseBody($orderManagementResponse); 36 | 37 | $voidUrl = \sprintf('%s/release-remaining-authorization', $baseUrl); 38 | 39 | if (empty($order['captures'])) { 40 | $voidUrl = \sprintf('%s/cancel', $baseUrl); 41 | } 42 | 43 | $response = $this->sendRequest('POST', $voidUrl, $data); 44 | 45 | return new VoidResponse( 46 | $this, 47 | $this->getResponseBody($response), 48 | $response->getStatusCode() 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Message/VoidResponse.php: -------------------------------------------------------------------------------- 1 | statusCode = $statusCode; 23 | } 24 | 25 | public function getStatusCode(): int 26 | { 27 | return $this->statusCode; 28 | } 29 | 30 | public function isSuccessful(): bool 31 | { 32 | return parent::isSuccessful() && 204 === $this->statusCode; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/WidgetOptions.php: -------------------------------------------------------------------------------- 1 | 'eCommerce', 17 | 'allow_separate_shipping_address' => false, 18 | 'color_button' => null, 19 | 'color_button_text' => null, 20 | 'color_checkbox' => null, 21 | 'color_checkbox_checkmark' => null, 22 | 'color_header' => null, 23 | 'color_link' => null, 24 | 'date_of_birth_mandatory' => false, 25 | 'shipping_details' => null, 26 | 'title_mandatory' => false, 27 | 'additional_checkbox' => null, 28 | 'radius_border' => null, 29 | 'show_subtotal_detail' => false, 30 | 'require_validate_callback_success' => false, 31 | 'allow_global_billing_countries' => false, 32 | ]; 33 | 34 | return new self(\array_merge($defaults, \array_intersect_key($data, $defaults))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/AuthenticationRequestHeaderProviderTest.php: -------------------------------------------------------------------------------- 1 | createMock(AbstractRequest::class); 15 | 16 | $userName = 'foobar'; 17 | $request->expects(self::once())->method('getUsername')->willReturn($userName); 18 | $secret = 'barbaz'; 19 | $request->expects(self::once())->method('getSecret')->willReturn($secret); 20 | 21 | self::assertEquals( 22 | [ 23 | 'Authorization' => \sprintf( 24 | 'Basic %s', 25 | \base64_encode( 26 | \sprintf( 27 | '%s:%s', 28 | $userName, 29 | $secret 30 | ) 31 | ) 32 | ), 33 | ], 34 | (new AuthenticationRequestHeaderProvider())->getHeaders($request) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/ExpectedAuthorizationHeaderTrait.php: -------------------------------------------------------------------------------- 1 | \sprintf( 17 | 'Basic %s', 18 | \base64_encode( 19 | \sprintf( 20 | '%s:%s', 21 | RequestTestCase::USERNAME, 22 | RequestTestCase::SECRET 23 | ) 24 | ) 25 | ), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/GatewayTest.php: -------------------------------------------------------------------------------- 1 | gateway = new Gateway($this->getHttpClient(), $this->getHttpRequest()); 27 | } 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function baseUrlDataProvider(): array 33 | { 34 | return [ 35 | [true, Gateway::API_VERSION_EUROPE, Gateway::EU_TEST_BASE_URL], 36 | [false, Gateway::API_VERSION_EUROPE, Gateway::EU_BASE_URL], 37 | [true, Gateway::API_VERSION_NORTH_AMERICA, Gateway::NA_TEST_BASE_URL], 38 | [false, Gateway::API_VERSION_NORTH_AMERICA, Gateway::NA_BASE_URL], 39 | ]; 40 | } 41 | 42 | public function testAcknowledge() 43 | { 44 | $this->assertInstanceOf(AcknowledgeRequest::class, $this->gateway->acknowledge()); 45 | } 46 | 47 | public function testAuthorize() 48 | { 49 | $this->assertInstanceOf(AuthorizeRequest::class, $this->gateway->authorize()); 50 | } 51 | 52 | public function testCapture() 53 | { 54 | $this->assertInstanceOf(CaptureRequest::class, $this->gateway->capture()); 55 | } 56 | 57 | public function testExtendAuthorizationWillReturnInstanceOfExtendAuthorization() 58 | { 59 | $request = $this->gateway->extendAuthorization(['transactionReference' => 'foobar']); 60 | 61 | $this->assertInstanceOf(ExtendAuthorizationRequest::class, $request); 62 | self::assertSame('foobar', $request->getTransactionReference()); 63 | } 64 | 65 | public function testFetchTransaction() 66 | { 67 | $this->assertInstanceOf(FetchTransactionRequest::class, $this->gateway->fetchTransaction()); 68 | } 69 | 70 | /** 71 | * @dataProvider baseUrlDataProvider 72 | * 73 | * @param bool $testMode 74 | * @param string $region 75 | * @param string $expectedUrl 76 | */ 77 | public function testInitialisationWillSetCorrectBaseUrl($testMode, $region, $expectedUrl) 78 | { 79 | $this->gateway->initialize(['testMode' => $testMode, 'api_region' => $region]); 80 | self::assertEquals($expectedUrl, $this->gateway->getParameter('base_url')); 81 | } 82 | 83 | /** 84 | * @dataProvider baseUrlDataProvider 85 | * 86 | * @param bool $testMode 87 | * @param string $region 88 | * @param string $expectedUrl 89 | */ 90 | public function testSetTestModeWillSetCorrectBaseUrl($testMode, $region, $expectedUrl) 91 | { 92 | $this->gateway->initialize(['api_region' => $region]); 93 | $this->gateway->setTestMode($testMode); 94 | self::assertEquals($expectedUrl, $this->gateway->getParameter('base_url')); 95 | } 96 | 97 | public function testRefund() 98 | { 99 | $this->assertInstanceOf(RefundRequest::class, $this->gateway->refund()); 100 | } 101 | 102 | public function testUpdateCustomerAddress() 103 | { 104 | $this->assertInstanceOf(UpdateCustomerAddressRequest::class, $this->gateway->updateCustomerAddress()); 105 | } 106 | 107 | public function testUpdateMerchantReferences() 108 | { 109 | $this->assertInstanceOf(UpdateMerchantReferencesRequest::class, $this->gateway->updateMerchantReferences()); 110 | } 111 | 112 | public function testUpdateTransaction() 113 | { 114 | $this->assertInstanceOf(UpdateTransactionRequest::class, $this->gateway->updateTransaction()); 115 | } 116 | 117 | public function testVoid() 118 | { 119 | $this->assertInstanceOf(VoidRequest::class, $this->gateway->void()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /tests/ItemBagTest.php: -------------------------------------------------------------------------------- 1 | createMock(ItemInterface::class); 15 | 16 | $itemBag = new ItemBag(); 17 | $itemBag->add($item); 18 | 19 | self::assertSame([$item], $itemBag->all()); 20 | } 21 | 22 | public function testAddNonItem() 23 | { 24 | $itemArray = ['tax_rate' => 1000]; 25 | 26 | $itemBag = new ItemBag(); 27 | $itemBag->add($itemArray); 28 | 29 | self::assertEquals($itemArray, $itemBag->all()[0]->getParameters()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/ItemTest.php: -------------------------------------------------------------------------------- 1 | setTaxRate($taxRate); 22 | $item->setTotalTaxAmount($totalTaxAmount); 23 | $item->setTotalAmount($totalAmount); 24 | $item->setTotalDiscountAmount($totalDiscountAmount); 25 | $item->setType($type); 26 | $item->setMerchantData($merchantData); 27 | 28 | self::assertEquals($taxRate, $item->getTaxRate()); 29 | self::assertEquals($totalTaxAmount, $item->getTotalTaxAmount()); 30 | self::assertEquals($totalAmount, $item->getTotalAmount()); 31 | self::assertEquals($totalDiscountAmount, $item->getTotalDiscountAmount()); 32 | self::assertEquals($type, $item->getType()); 33 | self::assertEquals($merchantData, $item->getMerchantData()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Message/AbstractRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 22 | $httpRequest = $this->getHttpRequest(); 23 | 24 | $this->request = new class ($httpClient, $httpRequest) extends AbstractRequest 25 | { 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function sendData($data) 30 | { 31 | return []; 32 | } 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | public function getData() 38 | { 39 | return []; 40 | } 41 | }; 42 | } 43 | 44 | public function testGetAmountWithDecimalStringShouldReturnCorrectValue() 45 | { 46 | $amount = '5.25'; 47 | $currencyIso = 'EUR'; 48 | 49 | $this->request->setCurrency($currencyIso); 50 | $this->request->setAmount($amount); 51 | 52 | self::assertSame('525', $this->request->getAmount()->getAmount()); 53 | } 54 | 55 | public function testGetResponseBodyWillReturnArray() 56 | { 57 | $httpClient = $this->getHttpClient(); 58 | $httpRequest = $this->getHttpRequest(); 59 | 60 | $request = new class ($httpClient, $httpRequest) extends AbstractRequest 61 | { 62 | public function sendData($data) 63 | { 64 | return []; 65 | } 66 | 67 | public function getData() 68 | { 69 | return []; 70 | } 71 | 72 | // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod 73 | public function getResponseBody(ResponseInterface $response): array 74 | { 75 | return parent::getResponseBody($response); // TODO: Change the autogenerated stub 76 | } 77 | }; 78 | 79 | $response = $this->createMock(ResponseInterface::class); 80 | $stream = $this->createMock(StreamInterface::class); 81 | 82 | $response->expects(self::once()) 83 | ->method('getBody') 84 | ->willReturn($stream); 85 | 86 | $decodedResponse = ['foo' => 'bar']; 87 | $stream->expects(self::once()) 88 | ->method('getContents') 89 | ->willReturn(\json_encode($decodedResponse)); 90 | 91 | self::assertSame($decodedResponse, $request->getResponseBody($response)); 92 | } 93 | 94 | public function testGetResponseBodyWillReturnArrayIfResponseIsEmtpy() 95 | { 96 | $httpClient = $this->getHttpClient(); 97 | $httpRequest = $this->getHttpRequest(); 98 | 99 | $request = new class ($httpClient, $httpRequest) extends AbstractRequest 100 | { 101 | public function sendData($data) 102 | { 103 | return []; 104 | } 105 | 106 | public function getData() 107 | { 108 | return []; 109 | } 110 | 111 | public function getResponseBody(ResponseInterface $response): array 112 | { 113 | return parent::getResponseBody($response); // TODO: Change the autogenerated stub 114 | } 115 | }; 116 | 117 | $response = $this->createMock(ResponseInterface::class); 118 | $stream = $this->createMock(StreamInterface::class); 119 | 120 | $response->expects(self::once()) 121 | ->method('getBody') 122 | ->willReturn($stream); 123 | 124 | $stream->expects(self::once()) 125 | ->method('getContents') 126 | ->willReturn(null); 127 | 128 | self::assertSame([], $request->getResponseBody($response)); 129 | } 130 | 131 | public function testGetTaxAmountWithDecimalStringShouldReturnCorrectValue() 132 | { 133 | $taxAmount = '5.25'; 134 | $currencyIso = 'EUR'; 135 | 136 | $this->request->setCurrency($currencyIso); 137 | $this->request->setTaxAmount($taxAmount); 138 | 139 | self::assertSame('525', $this->request->getTaxAmount()->getAmount()); 140 | } 141 | 142 | public function testGetters() 143 | { 144 | $locale = 'nl_NL'; 145 | $taxAmount = 500; 146 | $currencyIso = 'EUR'; 147 | 148 | $this->request->setCurrency($currencyIso); 149 | $this->request->setLocale($locale); 150 | $this->request->setTaxAmount(new Money($taxAmount, new Currency($currencyIso))); 151 | 152 | self::assertSame($locale, $this->request->getLocale()); 153 | self::assertSame((string) $taxAmount, $this->request->getTaxAmount()->getAmount()); 154 | } 155 | 156 | public function testSetItemsWithArray() 157 | { 158 | $itemsArray = [['tax_rate' => 10]]; 159 | 160 | $this->request->setItems($itemsArray); 161 | 162 | self::assertEquals(new ItemBag($itemsArray), $this->request->getParameters()['items']); 163 | } 164 | 165 | public function testSetItemsWithItemBag() 166 | { 167 | $itemBag = $this->createMock(ItemBag::class); 168 | 169 | $this->request->setItems($itemBag); 170 | 171 | self::assertSame($itemBag, $this->request->getParameters()['items']); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tests/Message/AbstractResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockForAbstractClass( 14 | AbstractResponse::class, 15 | [$this->getMockRequest(), ['order_id' => 'foo']] 16 | ); 17 | 18 | self::assertEquals('foo', $response->getTransactionReference()); 19 | } 20 | 21 | public function testGetTransactionReferenceReturnsNullIfNoOrderIdExists() 22 | { 23 | $response = $this->getMockForAbstractClass( 24 | AbstractResponse::class, 25 | [$this->getMockRequest(), []] 26 | ); 27 | 28 | self::assertNull($response->getTransactionReference()); 29 | } 30 | 31 | public function testIsSuccessfulWillReturnFalseIfResponseContainsErrors() 32 | { 33 | $response = $this->getMockForAbstractClass( 34 | AbstractResponse::class, 35 | [$this->getMockRequest(), ['error_code' => 'foo']] 36 | ); 37 | 38 | self::assertFalse($response->isSuccessful()); 39 | } 40 | 41 | public function testIsSuccessfulWillReturnTrueIfResponseContainsNoErrors() 42 | { 43 | $response = $this->getMockForAbstractClass(AbstractResponse::class, [$this->getMockRequest(), []]); 44 | 45 | self::assertTrue($response->isSuccessful()); 46 | } 47 | 48 | public function testGetMessageWillReturnErrorMessage() 49 | { 50 | $response = $this->getMockForAbstractClass( 51 | AbstractResponse::class, 52 | [$this->getMockRequest(), ['error_message' => 'oh noes!']] 53 | ); 54 | 55 | self::assertSame('oh noes!', $response->getMessage()); 56 | } 57 | 58 | public function testGetMessageWillReturnNullIfResponseContainsNoErrorMessage() 59 | { 60 | $response = $this->getMockForAbstractClass(AbstractResponse::class, [$this->getMockRequest(), []]); 61 | 62 | self::assertNull($response->getMessage()); 63 | } 64 | 65 | public function testGetCodeWillReturnErrorCode() 66 | { 67 | $response = $this->getMockForAbstractClass( 68 | AbstractResponse::class, 69 | [$this->getMockRequest(), ['error_code' => 'oh_noes']] 70 | ); 71 | 72 | self::assertSame('oh_noes', $response->getCode()); 73 | } 74 | 75 | public function testGetCodeWillReturnNullIfResponseContainsNoErrorCode() 76 | { 77 | $response = $this->getMockForAbstractClass(AbstractResponse::class, [$this->getMockRequest(), []]); 78 | 79 | self::assertNull($response->getCode()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/Message/AcknowledgeRequestTest.php: -------------------------------------------------------------------------------- 1 | acknowledgeRequest = new AcknowledgeRequest($this->httpClient, $this->getHttpRequest()); 19 | } 20 | 21 | public function testGetData() 22 | { 23 | $this->acknowledgeRequest->initialize(['transactionReference' => 'foo']); 24 | 25 | self::assertNull($this->acknowledgeRequest->getData()); 26 | } 27 | 28 | public function testSendData() 29 | { 30 | $inputData = ['request-data' => 'yey?']; 31 | $expectedData = []; 32 | 33 | $response = $this->setExpectedPostRequest( 34 | $inputData, 35 | $expectedData, 36 | self::BASE_URL . '/ordermanagement/v1/orders/foo/acknowledge' 37 | ); 38 | 39 | $response->expects(self::once())->method('getStatusCode')->willReturn(204); 40 | 41 | $this->acknowledgeRequest->initialize( 42 | [ 43 | 'base_url' => self::BASE_URL, 44 | 'username' => self::USERNAME, 45 | 'secret' => self::SECRET, 46 | 'transactionReference' => 'foo', 47 | ] 48 | ); 49 | 50 | $acknowledgeResponse = $this->acknowledgeRequest->sendData($inputData); 51 | 52 | self::assertInstanceOf(AcknowledgeResponse::class, $acknowledgeResponse); 53 | self::assertSame($expectedData, $acknowledgeResponse->getData()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Message/AcknowledgeResponseTest.php: -------------------------------------------------------------------------------- 1 | createMock(RequestInterface::class); 27 | $acknowledgeResponse = new AcknowledgeResponse($request, [], 200); 28 | 29 | self::assertNull($acknowledgeResponse->getCode()); 30 | } 31 | 32 | public function testGetMessageWillReturnErrorMessage() 33 | { 34 | $request = $this->createMock(RequestInterface::class); 35 | $acknowledgeResponse = new AcknowledgeResponse($request, ['error_message' => 'oh noes!'], 200); 36 | 37 | self::assertSame('oh noes!', $acknowledgeResponse->getMessage()); 38 | } 39 | 40 | public function testGetTransactionReferenceReturnsIdFromOrder() 41 | { 42 | $request = $this->createMock(RequestInterface::class); 43 | $acknowledgeResponse = new AcknowledgeResponse($request, ['order_id' => 'foo'], 200); 44 | 45 | self::assertEquals('foo', $acknowledgeResponse->getTransactionReference()); 46 | } 47 | 48 | /** 49 | * @dataProvider responseCodeProvider 50 | * 51 | * @param string $responseCode 52 | * @param bool $expectedResult 53 | */ 54 | public function testIsSuccessfulWillReturnCorrectStateWithResponseCode($responseCode, $expectedResult) 55 | { 56 | $request = $this->createMock(RequestInterface::class); 57 | 58 | $acknowledgeResponse = new AcknowledgeResponse($request, [], $responseCode); 59 | 60 | self::assertEquals($expectedResult, $acknowledgeResponse->isSuccessful()); 61 | } 62 | 63 | public function testIsSuccessfulWillReturnFalseWhenErrorIsFound() 64 | { 65 | $request = $this->createMock(RequestInterface::class); 66 | 67 | $acknowledgeResponse = new AcknowledgeResponse($request, ['error_code' => 'foobar'], 200); 68 | 69 | self::assertFalse($acknowledgeResponse->isSuccessful()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Message/AddressTest.php: -------------------------------------------------------------------------------- 1 | self::ORGANIZATION_NAME, 38 | 'reference' => self::REFERENCE, 39 | 'attention' => self::ATTENTION, 40 | 'family_name' => self::FAMILY_NAME, 41 | 'given_name' => self::GIVEN_NAME, 42 | 'email' => self::EMAIL, 43 | 'title' => self::TITLE, 44 | 'street_address' => self::STREET_ADDRESS_1, 45 | 'street_address2' => self::STREET_ADDRESS_2, 46 | 'street_name' => self::STREET, 47 | 'house_extension' => self::HOUSE_EXTENSION, 48 | 'street_number' => self::STREET_NUMBER, 49 | 'postal_code' => self::POSTAL_CODE, 50 | 'city' => self::CITY, 51 | 'region' => self::REGION, 52 | 'phone' => self::PHONE, 53 | 'country' => self::COUNTRY, 54 | ], 55 | [ 56 | 'organization_name' => self::ORGANIZATION_NAME, 57 | 'reference' => self::REFERENCE, 58 | 'attention' => self::ATTENTION, 59 | 'family_name' => self::FAMILY_NAME, 60 | 'given_name' => self::GIVEN_NAME, 61 | 'email' => self::EMAIL, 62 | 'title' => self::TITLE, 63 | 'street_address' => self::STREET_ADDRESS_1, 64 | 'street_address2' => self::STREET_ADDRESS_2, 65 | 'street_name' => self::STREET, 66 | 'house_extension' => self::HOUSE_EXTENSION, 67 | 'street_number' => self::STREET_NUMBER, 68 | 'postal_code' => self::POSTAL_CODE, 69 | 'city' => self::CITY, 70 | 'region' => self::REGION, 71 | 'phone' => self::PHONE, 72 | 'country' => self::COUNTRY, 73 | ], 74 | ], 75 | [ 76 | [ 77 | 'organization_name' => self::ORGANIZATION_NAME, 78 | 'reference' => self::REFERENCE, 79 | 'attention' => self::ATTENTION, 80 | 'family_name' => self::FAMILY_NAME, 81 | 'given_name' => self::GIVEN_NAME, 82 | 'email' => self::EMAIL, 83 | 'title' => self::TITLE, 84 | 'street_name' => self::STREET, 85 | 'house_extension' => self::HOUSE_EXTENSION, 86 | 'street_number' => self::STREET_NUMBER, 87 | 'postal_code' => self::POSTAL_CODE, 88 | 'city' => self::CITY, 89 | 'region' => self::REGION, 90 | 'phone' => self::PHONE, 91 | 'country' => self::COUNTRY, 92 | ], 93 | [ 94 | 'organization_name' => self::ORGANIZATION_NAME, 95 | 'reference' => self::REFERENCE, 96 | 'attention' => self::ATTENTION, 97 | 'family_name' => self::FAMILY_NAME, 98 | 'given_name' => self::GIVEN_NAME, 99 | 'email' => self::EMAIL, 100 | 'title' => self::TITLE, 101 | 'street_address' => null, 102 | 'street_address2' => null, 103 | 'street_name' => self::STREET, 104 | 'house_extension' => self::HOUSE_EXTENSION, 105 | 'street_number' => self::STREET_NUMBER, 106 | 'postal_code' => self::POSTAL_CODE, 107 | 'city' => self::CITY, 108 | 'region' => self::REGION, 109 | 'phone' => self::PHONE, 110 | 'country' => 'NL', 111 | ], 112 | ], 113 | [ 114 | [ 115 | 'organization_name' => self::ORGANIZATION_NAME, 116 | 'reference' => self::REFERENCE, 117 | 'attention' => self::ATTENTION, 118 | 'family_name' => self::FAMILY_NAME, 119 | 'given_name' => self::GIVEN_NAME, 120 | 'email' => self::EMAIL, 121 | 'title' => self::TITLE, 122 | 'street_name' => self::STREET, 123 | 'house_extension' => self::HOUSE_EXTENSION, 124 | 'street_number' => self::STREET_NUMBER, 125 | 'postal_code' => self::POSTAL_CODE, 126 | 'city' => self::CITY, 127 | 'region' => self::REGION, 128 | 'phone' => self::PHONE, 129 | 'country' => self::COUNTRY, 130 | self::FAMILY_NAME => self::GIVEN_NAME, 131 | ], 132 | [ 133 | 'organization_name' => self::ORGANIZATION_NAME, 134 | 'reference' => self::REFERENCE, 135 | 'attention' => self::ATTENTION, 136 | 'family_name' => self::FAMILY_NAME, 137 | 'given_name' => self::GIVEN_NAME, 138 | 'email' => self::EMAIL, 139 | 'title' => self::TITLE, 140 | 'street_address' => null, 141 | 'street_address2' => null, 142 | 'street_name' => self::STREET, 143 | 'house_extension' => self::HOUSE_EXTENSION, 144 | 'street_number' => self::STREET_NUMBER, 145 | 'postal_code' => self::POSTAL_CODE, 146 | 'city' => self::CITY, 147 | 'region' => self::REGION, 148 | 'phone' => self::PHONE, 149 | 'country' => self::COUNTRY, 150 | ], 151 | ], 152 | ]; 153 | } 154 | 155 | /** 156 | * @dataProvider fromArrayDataProvider 157 | * 158 | * @param array $data 159 | * @param array $expectedOutcome 160 | */ 161 | public function testFromArrayShoulReturnArrayWithCorrectKeys(array $data, array $expectedOutcome) 162 | { 163 | self::assertEquals($expectedOutcome, Address::fromArray($data)->getArrayCopy()); 164 | } 165 | 166 | /** 167 | * @return array 168 | */ 169 | public function toArrayDataProvider(): array 170 | { 171 | return [ 172 | [ 173 | [ 174 | 'organization_name' => self::ORGANIZATION_NAME, 175 | 'reference' => self::REFERENCE, 176 | 'attention' => self::ATTENTION, 177 | 'family_name' => self::FAMILY_NAME, 178 | 'given_name' => self::GIVEN_NAME, 179 | 'email' => self::EMAIL, 180 | 'title' => self::TITLE, 181 | 'street_address' => self::STREET_ADDRESS_1, 182 | 'street_address2' => self::STREET_ADDRESS_2, 183 | 'street_name' => self::STREET, 184 | 'house_extension' => self::HOUSE_EXTENSION, 185 | 'street_number' => self::STREET_NUMBER, 186 | 'postal_code' => self::POSTAL_CODE, 187 | 'city' => self::CITY, 188 | 'region' => self::REGION, 189 | 'phone' => self::PHONE, 190 | 'country' => self::COUNTRY, 191 | ], 192 | [ 193 | 'organization_name' => self::ORGANIZATION_NAME, 194 | 'reference' => self::REFERENCE, 195 | 'attention' => self::ATTENTION, 196 | 'family_name' => self::FAMILY_NAME, 197 | 'given_name' => self::GIVEN_NAME, 198 | 'email' => self::EMAIL, 199 | 'title' => self::TITLE, 200 | 'street_address' => self::STREET_ADDRESS_1, 201 | 'street_address2' => self::STREET_ADDRESS_2, 202 | 'street_name' => self::STREET, 203 | 'house_extension' => self::HOUSE_EXTENSION, 204 | 'street_number' => self::STREET_NUMBER, 205 | 'postal_code' => self::POSTAL_CODE, 206 | 'city' => self::CITY, 207 | 'region' => self::REGION, 208 | 'phone' => self::PHONE, 209 | 'country' => self::COUNTRY, 210 | ], 211 | [], 212 | ], 213 | [ 214 | [ 215 | 'organization_name' => '', 216 | 'reference' => false, 217 | 'attention' => self::ATTENTION, 218 | 'family_name' => self::FAMILY_NAME, 219 | 'given_name' => self::GIVEN_NAME, 220 | 'email' => self::EMAIL, 221 | 'title' => null, 222 | 'street_name' => self::STREET, 223 | 'house_extension' => self::HOUSE_EXTENSION, 224 | 'street_number' => self::STREET_NUMBER, 225 | 'postal_code' => self::POSTAL_CODE, 226 | 'city' => self::CITY, 227 | 'region' => self::REGION, 228 | 'phone' => self::PHONE, 229 | 'country' => self::COUNTRY, 230 | self::FAMILY_NAME => self::GIVEN_NAME, 231 | ], 232 | [ 233 | 'attention' => self::ATTENTION, 234 | 'family_name' => self::FAMILY_NAME, 235 | 'given_name' => self::GIVEN_NAME, 236 | 'email' => self::EMAIL, 237 | 'street_name' => self::STREET, 238 | 'house_extension' => self::HOUSE_EXTENSION, 239 | 'street_number' => self::STREET_NUMBER, 240 | 'postal_code' => self::POSTAL_CODE, 241 | 'city' => self::CITY, 242 | 'region' => self::REGION, 243 | 'phone' => self::PHONE, 244 | 'country' => self::COUNTRY, 245 | 'street_address' => null, 246 | 'street_address2' => null, 247 | ], 248 | ['title', 'organization_name', 'reference'], 249 | ], 250 | ]; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /tests/Message/AuthorizeRequestTest.php: -------------------------------------------------------------------------------- 1 | authorizeRequest = new AuthorizeRequest($this->httpClient, $this->getHttpRequest()); 27 | } 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function invalidRequestDataProvider(): array 33 | { 34 | $itemBag = $this->createMock(ItemBag::class); 35 | $itemBag->method('getIterator')->willReturn(new \ArrayIterator([])); 36 | 37 | $data = \array_merge( 38 | [ 39 | 'currency' => 'EUR', 40 | 'amount' => true, 41 | 'items' => $itemBag, 42 | 'locale' => true, 43 | 'purchase_country' => true, 44 | 'tax_amount' => new Money(1, new Currency('EUR')), 45 | ], 46 | \array_fill_keys(\array_keys($this->getMinimalValidMerchantUrlData()), true) 47 | ); 48 | 49 | $cases = []; 50 | 51 | foreach ($data as $key => $value) { 52 | $cases[] = [\array_diff_key($data, [$key => $value])]; 53 | } 54 | 55 | return $cases; 56 | } 57 | 58 | public function testGetDataWillReturnCorrectData() 59 | { 60 | $this->authorizeRequest->initialize( 61 | \array_merge( 62 | [ 63 | 'currency' => 'EUR', 64 | 'locale' => 'nl_NL', 65 | 'amount' => '100.00', 66 | 'tax_amount' => 21, 67 | 'purchase_country' => 'NL', 68 | ], 69 | $this->getCompleteValidMerchantUrlData() 70 | ) 71 | ); 72 | 73 | $this->authorizeRequest->setItems([$this->getItemMock()]); 74 | 75 | self::assertEquals( 76 | [ 77 | 'locale' => 'nl-NL', 78 | 'order_amount' => 10000, 79 | 'order_tax_amount' => 2100, 80 | 'order_lines' => [$this->getExpectedOrderLine()], 81 | 'merchant_urls' => $this->getCompleteExpectedMerchantUrlData(), 82 | 'purchase_country' => 'NL', 83 | 'purchase_currency' => 'EUR', 84 | ], 85 | $this->authorizeRequest->getData() 86 | ); 87 | } 88 | 89 | /** 90 | * @dataProvider invalidRequestDataProvider 91 | * 92 | * @param array $requestData 93 | * 94 | * @throws InvalidRequestException 95 | */ 96 | public function testGetDataWillThrowExceptionForInvalidRequest(array $requestData) 97 | { 98 | $this->authorizeRequest->initialize($requestData); 99 | 100 | $this->expectException(InvalidRequestException::class); 101 | $this->authorizeRequest->getData(); 102 | } 103 | 104 | public function testGetDataWithAddressesWillReturnCorrectData() 105 | { 106 | $organization = 'Foo inc'; 107 | $reference = 'ref'; 108 | $attention = 'quz'; 109 | $email = 'foo@bar.com'; 110 | $title = 'Mr.'; 111 | $streetAddress = 'Foo Street 1'; 112 | $streetAddress2 = 'App. 12A'; 113 | $streetName = 'Foo Street'; 114 | $houseExtension = 'C'; 115 | $streetNumber = '1'; 116 | $postalCode = '523354'; 117 | $city = 'Oss'; 118 | $region = 'NB'; 119 | $phone = '24234234'; 120 | $country = 'NL'; 121 | 122 | $shippingAddress = [ 123 | 'organization_name' => $organization, 124 | 'reference' => $reference, 125 | 'attention' => $attention, 126 | 'given_name' => 'foo', 127 | 'family_name' => 'bar', 128 | 'email' => $email, 129 | 'title' => $title, 130 | 'street_address' => $streetAddress, 131 | 'street_address2' => $streetAddress2, 132 | 'street_name' => $streetName, 133 | 'street_number' => $streetNumber, 134 | 'house_extension' => $houseExtension, 135 | 'postal_code' => $postalCode, 136 | 'city' => $city, 137 | 'region' => $region, 138 | 'phone' => $phone, 139 | 'country' => $country, 140 | ]; 141 | $billingAddress = [ 142 | 'organization_name' => $organization, 143 | 'reference' => $reference, 144 | 'attention' => $attention, 145 | 'given_name' => 'bar', 146 | 'family_name' => 'foo', 147 | 'email' => $email, 148 | 'title' => $title, 149 | 'street_address' => $streetAddress, 150 | 'street_address2' => $streetAddress2, 151 | 'street_name' => $streetName, 152 | 'street_number' => $streetNumber, 153 | 'house_extension' => $houseExtension, 154 | 'postal_code' => $postalCode, 155 | 'city' => $city, 156 | 'region' => $region, 157 | 'phone' => $phone, 158 | 'country' => $country, 159 | ]; 160 | 161 | $this->authorizeRequest->initialize( 162 | \array_merge( 163 | [ 164 | 'currency' => 'EUR', 165 | 'locale' => 'nl_NL', 166 | 'amount' => '100.00', 167 | 'tax_amount' => 21, 168 | 'purchase_country' => 'DE', 169 | ], 170 | $this->getMinimalValidMerchantUrlData() 171 | ) 172 | ); 173 | $this->authorizeRequest->setItems([$this->getItemMock()]); 174 | $this->authorizeRequest->setBillingAddress($billingAddress); 175 | $this->authorizeRequest->setShippingAddress($shippingAddress); 176 | 177 | self::assertEquals( 178 | [ 179 | 'locale' => 'nl-NL', 180 | 'order_amount' => 10000, 181 | 'order_tax_amount' => 2100, 182 | 'order_lines' => [$this->getExpectedOrderLine()], 183 | 'merchant_urls' => $this->getMinimalExpectedMerchantUrlData(), 184 | 'purchase_country' => 'DE', 185 | 'purchase_currency' => 'EUR', 186 | 'shipping_address' => $shippingAddress, 187 | 'billing_address' => $billingAddress, 188 | ], 189 | $this->authorizeRequest->getData() 190 | ); 191 | } 192 | 193 | public function testGetDataWithCustomerWillReturnCorrectData() 194 | { 195 | $customer = [ 196 | 'date_of_birth' => '1995-10-20', 197 | 'type' => 'organization', 198 | ]; 199 | 200 | $this->authorizeRequest->initialize( 201 | \array_merge( 202 | [ 203 | 'locale' => 'nl_NL', 204 | 'amount' => '100.00', 205 | 'tax_amount' => 21, 206 | 'currency' => 'EUR', 207 | 'purchase_country' => 'FR', 208 | ], 209 | $this->getCompleteValidMerchantUrlData() 210 | ) 211 | ); 212 | $this->authorizeRequest->setCustomer($customer); 213 | $this->authorizeRequest->setItems([$this->getItemMock()]); 214 | 215 | self::assertEquals( 216 | [ 217 | 'locale' => 'nl-NL', 218 | 'order_amount' => 10000, 219 | 'order_tax_amount' => 2100, 220 | 'order_lines' => [$this->getExpectedOrderLine()], 221 | 'merchant_urls' => $this->getCompleteExpectedMerchantUrlData(), 222 | 'purchase_country' => 'FR', 223 | 'purchase_currency' => 'EUR', 224 | 'customer' => $customer, 225 | ], 226 | $this->authorizeRequest->getData() 227 | ); 228 | } 229 | 230 | public function testGetDataWithOptionsWillReturnCorrectData() 231 | { 232 | $widgetOptions = [ 233 | 'acquiring_channel' => 'foo', 234 | 'allow_separate_shipping_address' => true, 235 | 'color_button' => '#FFFFF', 236 | 'color_button_text' => '#FFFFF', 237 | 'color_checkbox' => '#FFFFF', 238 | 'color_checkbox_checkmark' => '#FFFFF', 239 | 'color_header' => '#FFFFF', 240 | 'color_link' => '#FFFFF', 241 | 'date_of_birth_mandatory' => true, 242 | 'shipping_details' => 'Delivered within 1-3 working days', 243 | 'title_mandatory' => true, 244 | 'additional_checkbox' => [ 245 | 'text' => 'Please add me to the newsletter list', 246 | 'checked' => false, 247 | 'required' => false, 248 | ], 249 | 'radius_border' => '5px', 250 | 'show_subtotal_detail' => true, 251 | 'require_validate_callback_success' => true, 252 | 'allow_global_billing_countries' => false, 253 | ]; 254 | 255 | $this->authorizeRequest->initialize( 256 | \array_merge( 257 | [ 258 | 'locale' => 'nl_NL', 259 | 'amount' => '100.00', 260 | 'tax_amount' => 21, 261 | 'currency' => 'EUR', 262 | 'shipping_countries' => ['NL', 'DE'], 263 | 'purchase_country' => 'BE', 264 | ], 265 | $this->getMinimalValidMerchantUrlData() 266 | ) 267 | ); 268 | $this->authorizeRequest->setItems([$this->getItemMock()]); 269 | $this->authorizeRequest->setWidgetOptions($widgetOptions); 270 | 271 | self::assertEquals( 272 | [ 273 | 'locale' => 'nl-NL', 274 | 'order_amount' => 10000, 275 | 'order_tax_amount' => 2100, 276 | 'order_lines' => [$this->getExpectedOrderLine()], 277 | 'merchant_urls' => $this->getMinimalExpectedMerchantUrlData(), 278 | 'purchase_country' => 'BE', 279 | 'purchase_currency' => 'EUR', 280 | 'options' => $widgetOptions, 281 | 'shipping_countries' => ['NL', 'DE'], 282 | ], 283 | $this->authorizeRequest->getData() 284 | ); 285 | } 286 | 287 | public function testSendDataWillCreateOrderAndReturnResponse() 288 | { 289 | $inputData = ['request-data' => 'yey?']; 290 | $expectedData = ['response-data' => 'yey!']; 291 | 292 | $response = $this->setExpectedPostRequest($inputData, $expectedData, self::BASE_URL . '/checkout/v3/orders'); 293 | 294 | $response->expects(self::once())->method('getStatusCode')->willReturn(200); 295 | 296 | $this->authorizeRequest->initialize( 297 | [ 298 | 'base_url' => self::BASE_URL, 299 | 'username' => self::USERNAME, 300 | 'secret' => self::SECRET, 301 | ] 302 | ); 303 | $this->authorizeRequest->setRenderUrl('localhost/render'); 304 | 305 | $authorizeResponse = $this->authorizeRequest->sendData($inputData); 306 | 307 | self::assertInstanceOf(AuthorizeResponse::class, $authorizeResponse); 308 | self::assertSame($expectedData, $authorizeResponse->getData()); 309 | self::assertEquals('localhost/render', $authorizeResponse->getRedirectUrl()); 310 | } 311 | 312 | public function testSendDataWillFetchOrderAndReturnResponseIfTransactionIdAlreadySet() 313 | { 314 | $inputData = ['request-data' => 'yey?']; 315 | $expectedData = ['response-data' => 'yey!']; 316 | 317 | $response = $this->setExpectedGetRequest( 318 | $expectedData, 319 | self::BASE_URL . '/checkout/v3/orders/f60e69e8-464a-48c0-a452-6fd562540f37' 320 | ); 321 | 322 | $response->expects(self::once())->method('getStatusCode')->willReturn(200); 323 | 324 | $this->authorizeRequest->initialize( 325 | [ 326 | 'render_url' => 'foobar', 327 | 'base_url' => self::BASE_URL, 328 | 'username' => self::USERNAME, 329 | 'secret' => self::SECRET, 330 | 'transactionReference' => 'f60e69e8-464a-48c0-a452-6fd562540f37', 331 | ] 332 | ); 333 | 334 | $response = $this->authorizeRequest->sendData($inputData); 335 | 336 | self::assertInstanceOf(AuthorizeResponse::class, $response); 337 | self::assertSame($expectedData, $response->getData()); 338 | } 339 | 340 | public function testSendDataWillRaiseExceptionOnErrorResponses() 341 | { 342 | $response = $this->createMock(ResponseInterface::class); 343 | $this->httpClient->expects(self::once())->method('request')->willReturn($response); 344 | 345 | $response->expects(self::once())->method('getStatusCode')->willReturn(401); 346 | 347 | $responseMessage = 'FooBar'; 348 | $response->expects(self::once())->method('getReasonPhrase')->willReturn($responseMessage); 349 | 350 | $this->expectException(InvalidResponseException::class); 351 | $this->expectExceptionMessage($responseMessage); 352 | 353 | $this->authorizeRequest->sendData([]); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /tests/Message/AuthorizeResponseTest.php: -------------------------------------------------------------------------------- 1 | createMock(RequestInterface::class); 15 | 16 | $response = new AuthorizeResponse($request, []); 17 | 18 | self::assertFalse($response->isSuccessful()); 19 | } 20 | 21 | public function testResponseIsNonRedirectWithoutRenderUrl() 22 | { 23 | $response = new AuthorizeResponse($this->getMockRequest(), [], null); 24 | 25 | self::assertFalse($response->isRedirect()); 26 | } 27 | 28 | public function testResponseIsRedirectWithRenderUrl() 29 | { 30 | $request = $this->createMock(RequestInterface::class); 31 | $response = new AuthorizeResponse($request, [], 'localhost/return'); 32 | 33 | self::assertNull($response->getRedirectData()); 34 | self::assertEquals('GET', $response->getRedirectMethod()); 35 | self::assertEquals('localhost/return', $response->getRedirectUrl()); 36 | self::assertTrue($response->isRedirect()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Message/CaptureRequestTest.php: -------------------------------------------------------------------------------- 1 | captureRequest = new CaptureRequest($this->httpClient, $this->getHttpRequest()); 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public function invalidRequestDataProvider(): array 29 | { 30 | return [ 31 | [['transactionReference' => self::TRANSACTION_REF]], 32 | [['amount' => '10.00']], 33 | ]; 34 | } 35 | 36 | /** 37 | * @dataProvider validRequestDataProvider 38 | * 39 | * @param array|null $items 40 | * @param array $expectedItemData 41 | */ 42 | public function testGetDataWillReturnCorrectData($items, array $expectedItemData) 43 | { 44 | $this->captureRequest->initialize( 45 | [ 46 | 'transactionReference' => self::TRANSACTION_REF, 47 | 'amount' => '100', 48 | 'currency' => 'USD', 49 | ] 50 | ); 51 | $this->captureRequest->setItems($items); 52 | 53 | /** @noinspection PhpUnhandledExceptionInspection */ 54 | self::assertEquals( 55 | ['captured_amount' => 10000] + $expectedItemData, 56 | $this->captureRequest->getData() 57 | ); 58 | } 59 | 60 | /** 61 | * @dataProvider invalidRequestDataProvider 62 | * 63 | * @param array $requestData 64 | */ 65 | public function testGetDataWillThrowExceptionForInvalidRequest(array $requestData) 66 | { 67 | $this->captureRequest->initialize($requestData); 68 | 69 | $this->expectException(InvalidRequestException::class); 70 | 71 | /** @noinspection PhpUnhandledExceptionInspection */ 72 | $this->captureRequest->getData(); 73 | } 74 | 75 | public function testSendDataWillCreateCaptureAndReturnResponseWithCaptureData() 76 | { 77 | $requestdata = ['request-data' => 'yey?']; 78 | $responseData = ['response-data' => 'yey!']; 79 | 80 | $response = $this->setExpectedPostRequest( 81 | $requestdata, 82 | $responseData, 83 | self::BASE_URL . '/ordermanagement/v1/orders/' . self::TRANSACTION_REF . '/captures' 84 | ); 85 | $response->expects(self::once())->method('getStatusCode')->willReturn(204); 86 | 87 | $this->captureRequest->initialize( 88 | [ 89 | 'base_url' => self::BASE_URL, 90 | 'username' => self::USERNAME, 91 | 'secret' => self::SECRET, 92 | 'transactionReference' => self::TRANSACTION_REF, 93 | ] 94 | ); 95 | 96 | $captureResponse = $this->captureRequest->sendData($requestdata); 97 | 98 | self::assertInstanceOf(CaptureResponse::class, $captureResponse); 99 | self::assertSame($responseData, $captureResponse->getData()); 100 | } 101 | 102 | /** 103 | * @return array 104 | */ 105 | public function validRequestDataProvider(): array 106 | { 107 | return [ 108 | [null, []], // No item data should return result without order_line entry 109 | [[$this->getItemMock()], ['order_lines' => [$this->getExpectedOrderLine()]]], 110 | ]; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/Message/CaptureResponseTest.php: -------------------------------------------------------------------------------- 1 | createMock(RequestInterface::class); 27 | 28 | $response = new CaptureResponse($request, [], 'foo', 201); 29 | 30 | self::assertSame('foo', $response->getTransactionReference()); 31 | self::assertSame(201, $response->getStatusCode()); 32 | } 33 | 34 | /** 35 | * @dataProvider responseCodeProvider 36 | * 37 | * @param string $responseCode 38 | * @param bool $expectedResult 39 | */ 40 | public function testIsSuccessfulWillReturnCorrectStateWithResponseCode($responseCode, $expectedResult) 41 | { 42 | $request = $this->createMock(RequestInterface::class); 43 | 44 | $captureResponse = new CaptureResponse($request, [], '123', $responseCode); 45 | 46 | self::assertEquals($expectedResult, $captureResponse->isSuccessful()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Message/CustomerTest.php: -------------------------------------------------------------------------------- 1 | '1995-10-20', 20 | 'type' => 'organization', 21 | ], 22 | [ 23 | 'date_of_birth' => '1995-10-20', 24 | 'type' => 'organization', 25 | ], 26 | ], 27 | [ 28 | [], 29 | [ 30 | 'date_of_birth' => null, 31 | 'type' => 'person', 32 | ], 33 | ], 34 | [ 35 | ['foo' => 'bar'], 36 | [ 37 | 'date_of_birth' => null, 38 | 'type' => 'person', 39 | ], 40 | ], 41 | ]; 42 | } 43 | 44 | /** 45 | * @dataProvider dataProvider 46 | * 47 | * @param array $data 48 | * @param array $expectedOutcome 49 | */ 50 | public function testFromArrayShoulReturnArrayWithCorrectKeys($data, $expectedOutcome) 51 | { 52 | self::assertEquals($expectedOutcome, Customer::fromArray($data)->getArrayCopy()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Message/ExtendAuthorizationRequestTest.php: -------------------------------------------------------------------------------- 1 | extendAuthorizationRequest = new ExtendAuthorizationRequest($this->httpClient, $this->getHttpRequest()); 20 | } 21 | 22 | public function testGetDataThrowsExceptionWhenMissingTransactionReference() 23 | { 24 | $this->expectException(InvalidRequestException::class); 25 | 26 | $this->extendAuthorizationRequest->initialize([]); 27 | $this->extendAuthorizationRequest->getData(); 28 | } 29 | 30 | public function testGetDataWithInvalidDataWillReturnNull() 31 | { 32 | $this->extendAuthorizationRequest->initialize(['transactionReference' => 'foo']); 33 | 34 | self::assertNull($this->extendAuthorizationRequest->getData()); 35 | } 36 | 37 | public function testSendDataWillWillSendDataToKlarnaEndPointAndReturnCorrectResponse() 38 | { 39 | $response = $this->createMock(ResponseInterface::class); 40 | $stream = $this->createMock(StreamInterface::class); 41 | 42 | $this->httpClient->expects(self::once()) 43 | ->method('request') 44 | ->with( 45 | 'POST', 46 | \sprintf( 47 | '%s/ordermanagement/v1/orders/%s/extend-authorization-time', 48 | self::BASE_URL, 49 | 'foo' 50 | ), 51 | \array_merge( 52 | ['Content-Type' => 'application/json'], 53 | [ 54 | 'Authorization' => \sprintf( 55 | 'Basic %s', 56 | \base64_encode( 57 | \sprintf( 58 | '%s:%s', 59 | null, 60 | self::SECRET 61 | ) 62 | ) 63 | ), 64 | ] 65 | ), 66 | \json_encode([]) 67 | ) 68 | ->willReturn($response); 69 | 70 | $response->method('getBody')->willReturn($stream); 71 | $stream->method('getContents')->willReturn(\json_encode(['hello' => 'world'])); 72 | 73 | $this->extendAuthorizationRequest->initialize( 74 | [ 75 | 'base_url' => self::BASE_URL, 76 | 'secret' => self::SECRET, 77 | 'transactionReference' => 'foo', 78 | ] 79 | ); 80 | 81 | $extendAuthorizationResponse = $this->extendAuthorizationRequest->sendData([]); 82 | 83 | self::assertSame('foo', $extendAuthorizationResponse->getTransactionReference()); 84 | self::assertSame( 85 | [ 86 | 'hello' => 'world', 87 | 'order_id' => 'foo', 88 | ], 89 | $extendAuthorizationResponse->getData() 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/Message/ExtendAuthorizationResponseTest.php: -------------------------------------------------------------------------------- 1 | 'oh_noes'], false], 19 | [[], true], 20 | ]; 21 | } 22 | 23 | public function testGetters() 24 | { 25 | $request = $this->createMock(RequestInterface::class); 26 | 27 | $responseData = ['order_id' => 'foo']; 28 | $response = new ExtendAuthorizationResponse($request, $responseData); 29 | 30 | self::assertSame('foo', $response->getTransactionReference()); 31 | } 32 | 33 | /** 34 | * @dataProvider responseDataProvider 35 | * 36 | * @param array $responseData 37 | * @param bool $expected 38 | */ 39 | public function testIsSuccessfulWillReturnWhetherResponseIsSuccessfull($responseData, $expected) 40 | { 41 | $request = $this->createMock(RequestInterface::class); 42 | $response = new ExtendAuthorizationResponse($request, $responseData); 43 | 44 | self::assertEquals($expected, $response->isSuccessful()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Message/FetchTransactionRequestTest.php: -------------------------------------------------------------------------------- 1 | fetchTransactionRequest = new FetchTransactionRequest($this->httpClient, $this->getHttpRequest()); 24 | } 25 | 26 | public function testGetDataReturnsNull() 27 | { 28 | $this->fetchTransactionRequest->initialize(['transactionReference' => 'foo']); 29 | 30 | self::assertNull($this->fetchTransactionRequest->getData()); 31 | } 32 | 33 | public function testGetDataThrowsExceptionWhenMissingTransactionReference() 34 | { 35 | $this->expectException(InvalidRequestException::class); 36 | 37 | $this->fetchTransactionRequest->initialize([]); 38 | $this->fetchTransactionRequest->getData(); 39 | } 40 | 41 | public function testSendDataWillReturnResponseFromCheckoutApiForIncompleteOrder() 42 | { 43 | $expectedCheckoutData = ['status' => 'checkout_incomplete']; 44 | 45 | $response = $this->setExpectedGetRequest( 46 | $expectedCheckoutData, 47 | self::BASE_URL . '/checkout/v3/orders/foo' 48 | ); 49 | $response->expects(self::once())->method('getStatusCode')->willReturn(200); 50 | 51 | $this->fetchTransactionRequest->initialize( 52 | [ 53 | 'base_url' => self::BASE_URL, 54 | 'username' => self::USERNAME, 55 | 'secret' => self::SECRET, 56 | 'transactionReference' => 'foo', 57 | ] 58 | ); 59 | 60 | $fetchResponse = $this->fetchTransactionRequest->sendData([]); 61 | 62 | self::assertInstanceOf(FetchTransactionResponse::class, $fetchResponse); 63 | self::assertSame(['checkout' => $expectedCheckoutData], $fetchResponse->getData()); 64 | } 65 | 66 | public function testSendDataWillReturnResponseFromCheckoutApiForUnknownOrder() 67 | { 68 | $expectedCheckoutData = ['response-data' => 'nay!']; 69 | 70 | $response = $this->setExpectedGetRequest( 71 | $expectedCheckoutData, 72 | self::BASE_URL . '/checkout/v3/orders/foo' 73 | ); 74 | $response->expects(self::once())->method('getStatusCode')->willReturn(200); 75 | 76 | $this->fetchTransactionRequest->initialize( 77 | [ 78 | 'base_url' => self::BASE_URL, 79 | 'username' => self::USERNAME, 80 | 'secret' => self::SECRET, 81 | 'transactionReference' => 'foo', 82 | ] 83 | ); 84 | 85 | $fetchResponse = $this->fetchTransactionRequest->sendData([]); 86 | 87 | self::assertInstanceOf(FetchTransactionResponse::class, $fetchResponse); 88 | self::assertSame(['checkout' => $expectedCheckoutData], $fetchResponse->getData()); 89 | } 90 | 91 | public function testSendDataWillReturnResponseFromManagementApiForCompleteOrder() 92 | { 93 | $expectedCheckoutData = ['status' => 'checkout_complete']; 94 | $expectedManagementData = ['response-data' => 'yay!']; 95 | $response = $this->createMock(ResponseInterface::class); 96 | $stream = $this->createMock(StreamInterface::class); 97 | 98 | $this->httpClient->expects(self::exactly(2)) 99 | ->method('request') 100 | ->withConsecutive( 101 | [ 102 | 'GET', 103 | self::BASE_URL . '/checkout/v3/orders/foo', 104 | $this->getExpectedHeaders(), 105 | null, 106 | ], 107 | [ 108 | 'GET', 109 | self::BASE_URL . '/ordermanagement/v1/orders/foo', 110 | $this->getExpectedHeaders(), 111 | null, 112 | ] 113 | )->willReturn($response); 114 | 115 | $response->method('getBody')->willReturn($stream); 116 | $stream->expects(self::exactly(2)) 117 | ->method('getContents') 118 | ->willReturnOnConsecutiveCalls( 119 | \json_encode($expectedCheckoutData), 120 | \json_encode($expectedManagementData) 121 | ); 122 | 123 | $this->fetchTransactionRequest->initialize( 124 | [ 125 | 'base_url' => self::BASE_URL, 126 | 'username' => self::USERNAME, 127 | 'secret' => self::SECRET, 128 | 'transactionReference' => 'foo', 129 | ] 130 | ); 131 | 132 | $fetchResponse = $this->fetchTransactionRequest->sendData([]); 133 | 134 | self::assertInstanceOf(FetchTransactionResponse::class, $fetchResponse); 135 | self::assertSame( 136 | ['checkout' => $expectedCheckoutData, 'management' => $expectedManagementData], 137 | $fetchResponse->getData() 138 | ); 139 | } 140 | 141 | public function testSendDataWillReturnResponseFromManagementApiForDeletedCheckoutOrder() 142 | { 143 | $expectedCheckoutData = []; 144 | $expectedManagementData = ['response-data' => 'yay!']; 145 | $response = $this->createMock(ResponseInterface::class); 146 | $stream = $this->createMock(StreamInterface::class); 147 | 148 | $this->httpClient->expects(self::exactly(2)) 149 | ->method('request') 150 | ->withConsecutive( 151 | [ 152 | 'GET', 153 | self::BASE_URL . '/checkout/v3/orders/foo', 154 | $this->getExpectedHeaders(), 155 | null, 156 | ], 157 | [ 158 | 'GET', 159 | self::BASE_URL . '/ordermanagement/v1/orders/foo', 160 | $this->getExpectedHeaders(), 161 | null, 162 | ] 163 | )->willReturn($response); 164 | 165 | $response->method('getBody')->willReturn($stream); 166 | $stream->expects(self::exactly(2)) 167 | ->method('getContents') 168 | ->willReturnOnConsecutiveCalls( 169 | \json_encode($expectedCheckoutData), 170 | \json_encode($expectedManagementData) 171 | ); 172 | 173 | $response->expects(self::once())->method('getStatusCode')->willReturn(404); 174 | 175 | $this->fetchTransactionRequest->initialize( 176 | [ 177 | 'base_url' => self::BASE_URL, 178 | 'username' => self::USERNAME, 179 | 'secret' => self::SECRET, 180 | 'transactionReference' => 'foo', 181 | ] 182 | ); 183 | 184 | $fetchResponse = $this->fetchTransactionRequest->sendData([]); 185 | 186 | self::assertInstanceOf(FetchTransactionResponse::class, $fetchResponse); 187 | self::assertSame( 188 | ['checkout' => $expectedCheckoutData, 'management' => $expectedManagementData], 189 | $fetchResponse->getData() 190 | ); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /tests/Message/FetchTransactionResponseTest.php: -------------------------------------------------------------------------------- 1 | ['error_code' => 'oh_noes']], false], 19 | [[], false], 20 | [['checkout' => ['status' => 'all_is_well']], true], 21 | [['management' => ['error_code' => 'oh_noes']], false], 22 | [['management' => ['status' => 'all_is_well']], true], 23 | ]; 24 | } 25 | 26 | public function testGetTransactionReferenceForCheckoutTransaction() 27 | { 28 | $request = $this->createMock(RequestInterface::class); 29 | 30 | $responseData = ['checkout' => ['order_id' => 'foo']]; 31 | $response = new FetchTransactionResponse($request, $responseData); 32 | 33 | self::assertSame($responseData['checkout']['order_id'], $response->getTransactionReference()); 34 | } 35 | 36 | public function testGetTransactionReferenceForManagementTransaction() 37 | { 38 | $request = $this->createMock(RequestInterface::class); 39 | 40 | $responseData = ['management' => ['order_id' => 'foo']]; 41 | $response = new FetchTransactionResponse($request, $responseData); 42 | 43 | self::assertSame($responseData['management']['order_id'], $response->getTransactionReference()); 44 | } 45 | 46 | /** 47 | * @dataProvider responseDataProvider 48 | * 49 | * @param array $responseData 50 | * @param bool $expected 51 | */ 52 | public function testIsSuccessfulWillReturnWhetherResponseIsSuccessfull($responseData, $expected) 53 | { 54 | $request = $this->createMock(RequestInterface::class); 55 | 56 | $response = new FetchTransactionResponse($request, $responseData); 57 | 58 | self::assertEquals($expected, $response->isSuccessful()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Message/ItemDataTestTrait.php: -------------------------------------------------------------------------------- 1 | 'shipping_fee', 15 | 'name' => 'item-name', 16 | 'quantity' => 1, 17 | 'tax_rate' => 2003, 18 | 'total_amount' => 10000, 19 | 'total_tax_amount' => 20000, 20 | 'total_discount_amount' => 0, 21 | 'unit_price' => 10000, 22 | 'merchant_data' => 'foobar', 23 | ]; 24 | } 25 | 26 | protected function getItemMock(): MockObject 27 | { 28 | $item = $this->createMock(ItemInterface::class); 29 | $item->method('getType')->willReturn('shipping_fee'); 30 | $item->method('getName')->willReturn('item-name'); 31 | $item->method('getQuantity')->willReturn(1); 32 | $item->method('getTaxRate')->willReturn(20.03); 33 | $item->method('getQuantity')->willReturn(1); 34 | $item->method('getPrice')->willReturn(100); 35 | $item->method('getTotalAmount')->willReturn(100); 36 | $item->method('getTotalTaxAmount')->willReturn(200); 37 | $item->method('getTotalDiscountAmount')->willReturn(0); 38 | $item->method('getMerchantData')->willReturn('foobar'); 39 | 40 | return $item; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Message/MerchantUrlsDataTestTrait.php: -------------------------------------------------------------------------------- 1 | 'localhost/address-update', 15 | 'cancellation_terms' => 'localhost/cancellation-terms', 16 | 'checkout' => 'localhost/return', 17 | 'confirmation' => 'localhost/confirm', 18 | 'push' => 'localhost/notify', 19 | 'terms' => 'localhost/terms', 20 | 'validation' => 'localhost/validate', 21 | ]; 22 | } 23 | 24 | /** 25 | * @return array 26 | */ 27 | public function getCompleteValidMerchantUrlData(): array 28 | { 29 | return [ 30 | 'addressUpdateUrl' => 'localhost/address-update', 31 | 'cancellationTermsUrl' => 'localhost/cancellation-terms', 32 | 'returnUrl' => 'localhost/return', 33 | 'confirmationUrl' => 'localhost/confirm', 34 | 'notifyUrl' => 'localhost/notify', 35 | 'termsUrl' => 'localhost/terms', 36 | 'validationUrl' => 'localhost/validate', 37 | ]; 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | public function getMinimalExpectedMerchantUrlData(): array 44 | { 45 | return [ 46 | 'checkout' => 'localhost/return', 47 | 'confirmation' => 'localhost/return', 48 | 'push' => 'localhost/notify', 49 | 'terms' => 'localhost/terms', 50 | 'validation' => 'localhost/validate', 51 | ]; 52 | } 53 | 54 | /** 55 | * @return array 56 | */ 57 | public function getMinimalValidMerchantUrlData(): array 58 | { 59 | return [ 60 | 'returnUrl' => 'localhost/return', 61 | 'notifyUrl' => 'localhost/notify', 62 | 'termsUrl' => 'localhost/terms', 63 | 'validationUrl' => 'localhost/validate', 64 | ]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Message/RefundRequestTest.php: -------------------------------------------------------------------------------- 1 | refundRequest = new RefundRequest($this->httpClient, $this->getHttpRequest()); 21 | } 22 | 23 | /** 24 | * @return array 25 | */ 26 | public function invalidRequestDataProvider(): array 27 | { 28 | return [ 29 | [['transactionReference' => 'foo']], 30 | [['amount' => '10.00']], 31 | ]; 32 | } 33 | 34 | /** 35 | * @dataProvider validRequestDataProvider 36 | * 37 | * @param array|null $items 38 | * @param array $expectedItemData 39 | */ 40 | public function testGetDataWillReturnCorrectData($items, array $expectedItemData) 41 | { 42 | $this->refundRequest->initialize(['transactionReference' => 'foo', 'amount' => '10.00', 'currency' => 'USD']); 43 | $this->refundRequest->setItems($items); 44 | 45 | /** @noinspection PhpUnhandledExceptionInspection */ 46 | self::assertEquals( 47 | ['refunded_amount' => 1000] + $expectedItemData, 48 | $this->refundRequest->getData() 49 | ); 50 | } 51 | 52 | /** 53 | * @dataProvider invalidRequestDataProvider 54 | * 55 | * @param array $requestData 56 | */ 57 | public function testGetDataWillThrowExceptionForInvalidRequest(array $requestData) 58 | { 59 | $this->refundRequest->initialize($requestData); 60 | 61 | $this->expectException(InvalidRequestException::class); 62 | 63 | /** @noinspection PhpUnhandledExceptionInspection */ 64 | $this->refundRequest->getData(); 65 | } 66 | 67 | public function testSendDataWillCreateRefundAndReturnResponse() 68 | { 69 | $inputData = ['request-data' => 'yey?']; 70 | $expectedData = []; 71 | 72 | $response = $this->setExpectedPostRequest( 73 | $inputData, 74 | $expectedData, 75 | self::BASE_URL . '/ordermanagement/v1/orders/foo/refunds' 76 | ); 77 | 78 | $response->expects(self::once())->method('getStatusCode')->willReturn(204); 79 | 80 | $this->refundRequest->initialize( 81 | [ 82 | 'base_url' => self::BASE_URL, 83 | 'username' => self::USERNAME, 84 | 'secret' => self::SECRET, 85 | 'transactionReference' => 'foo', 86 | ] 87 | ); 88 | 89 | $refundResponse = $this->refundRequest->sendData($inputData); 90 | 91 | self::assertInstanceOf(RefundResponse::class, $refundResponse); 92 | self::assertSame($expectedData, $refundResponse->getData()); 93 | } 94 | 95 | /** 96 | * @return array 97 | */ 98 | public function validRequestDataProvider(): array 99 | { 100 | return [ 101 | [null, []], // No item data should return result without order_line entry 102 | [[$this->getItemMock()], ['order_lines' => [$this->getExpectedOrderLine()]]], 103 | ]; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/Message/RefundResponseTest.php: -------------------------------------------------------------------------------- 1 | createMock(RequestInterface::class); 27 | 28 | $response = new RefundResponse($request, [], 201); 29 | 30 | self::assertSame(201, $response->getStatusCode()); 31 | } 32 | 33 | /** 34 | * @dataProvider responseCodeProvider 35 | * 36 | * @param string $responseCode 37 | * @param bool $expectedResult 38 | */ 39 | public function testIsSuccessfulWillReturnCorrectStateWithResponseCode($responseCode, $expectedResult) 40 | { 41 | $request = $this->createMock(RequestInterface::class); 42 | 43 | $captureResponse = new RefundResponse($request, [], $responseCode); 44 | 45 | self::assertEquals($expectedResult, $captureResponse->isSuccessful()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Message/RequestTestCase.php: -------------------------------------------------------------------------------- 1 | httpClient = $this->createMock(ClientInterface::class); 27 | } 28 | 29 | /** 30 | * @param array $responseData 31 | * @param string $url 32 | * 33 | * @return ResponseInterface|MockObject 34 | */ 35 | protected function setExpectedGetRequest(array $responseData, $url) 36 | { 37 | return $this->setExpectedRequest('GET', $url, [], null, $responseData); 38 | } 39 | 40 | /** 41 | * @param array $inputData 42 | * @param array $responseData 43 | * @param string $url 44 | * 45 | * @return ResponseInterface|MockObject 46 | */ 47 | protected function setExpectedPatchRequest(array $inputData, array $responseData, $url) 48 | { 49 | return $this->setExpectedRequest( 50 | 'PATCH', 51 | $url, 52 | ['Content-Type' => 'application/json'], 53 | $inputData, 54 | $responseData 55 | ); 56 | } 57 | 58 | /** 59 | * @param array $inputData 60 | * @param array $responseData 61 | * @param string $url 62 | * 63 | * @return ResponseInterface|MockObject 64 | */ 65 | protected function setExpectedPostRequest(array $inputData, array $responseData, $url) 66 | { 67 | return $this->setExpectedRequest( 68 | 'POST', 69 | $url, 70 | ['Content-Type' => 'application/json'], 71 | $inputData, 72 | $responseData 73 | ); 74 | } 75 | 76 | /** 77 | * @param string $requestMethod 78 | * @param string $url 79 | * @param array $headers 80 | * @param array $inputData 81 | * @param array $responseData 82 | * 83 | * @return ResponseInterface|MockObject 84 | */ 85 | private function setExpectedRequest( 86 | $requestMethod, 87 | $url, 88 | array $headers, 89 | ?array $inputData = null, 90 | array $responseData 91 | ) { 92 | $response = $this->createMock(ResponseInterface::class); 93 | $stream = $this->createMock(StreamInterface::class); 94 | 95 | $this->httpClient->expects(self::once()) 96 | ->method('request') 97 | ->with( 98 | $requestMethod, 99 | $url, 100 | \array_merge( 101 | $headers, 102 | $this->getExpectedHeaders() 103 | ), 104 | null === $inputData ? null : \json_encode($inputData) 105 | ) 106 | ->willReturn($response); 107 | 108 | $response->method('getBody')->willReturn($stream); 109 | $stream->method('getContents')->willReturn(\json_encode($responseData)); 110 | 111 | return $response; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/Message/UpdateCustomerAddressRequestTest.php: -------------------------------------------------------------------------------- 1 | updateCustomerAddressRequest = new UpdateCustomerAddressRequest( 20 | $this->httpClient, 21 | $this->getHttpRequest() 22 | ); 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public function addressDataProvider(): array 29 | { 30 | return [ 31 | [ 32 | [ 33 | 'organization_name' => null, 34 | 'reference' => 'ref', 35 | 'attention' => 'quz', 36 | 'given_name' => 'foo', 37 | 'family_name' => 'bar', 38 | 'email' => 'foo@bar.com', 39 | 'title' => 'Mr.', 40 | 'street_address' => 'Foo Street 1', 41 | 'street_address2' => 'App. 12A', 42 | 'street_name' => 'Foo Street', 43 | 'street_number' => '1', 44 | 'house_extension' => 'C', 45 | 'postal_code' => '523354', 46 | 'city' => 'Oss', 47 | 'region' => 'NB', 48 | 'phone' => '24234234', 49 | 'country' => 'NL', 50 | ], 51 | [ 52 | 'organization_name' => null, 53 | 'reference' => 'ref', 54 | 'attention' => 'quz', 55 | 'given_name' => 'foo', 56 | 'family_name' => 'bar', 57 | 'email' => 'foo@bar.com', 58 | 'title' => 'Mr.', 59 | 'street_address' => 'Foo Street 1', 60 | 'street_address2' => 'App. 12A', 61 | 'street_name' => 'Foo Street', 62 | 'street_number' => '1', 63 | 'house_extension' => 'C', 64 | 'postal_code' => '523354', 65 | 'city' => 'Oss', 66 | 'region' => 'NB', 67 | 'phone' => '24234234', 68 | 'country' => 'NL', 69 | ], 70 | ], 71 | [ 72 | [ 73 | 'organization_name' => 'Foobar BV', 74 | 'reference' => 'ref', 75 | 'attention' => 'quz', 76 | 'given_name' => 'foo', 77 | 'family_name' => 'bar', 78 | 'email' => 'foo@bar.com', 79 | 'title' => 'Mr.', 80 | 'street_address' => 'Foo Street 1', 81 | 'street_address2' => 'App. 12A', 82 | 'street_name' => 'Foo Street', 83 | 'street_number' => '1', 84 | 'house_extension' => 'C', 85 | 'postal_code' => '523354', 86 | 'city' => 'Oss', 87 | 'region' => 'NB', 88 | 'phone' => '24234234', 89 | 'country' => 'NL', 90 | ], 91 | [ 92 | 'organization_name' => 'Foobar BV', 93 | 'reference' => 'ref', 94 | 'attention' => 'quz', 95 | 'given_name' => 'foo', 96 | 'family_name' => 'bar', 97 | 'email' => 'foo@bar.com', 98 | 'title' => 'Mr.', 99 | 'street_address' => 'Foo Street 1', 100 | 'street_address2' => 'App. 12A', 101 | 'street_name' => 'Foo Street', 102 | 'street_number' => '1', 103 | 'house_extension' => 'C', 104 | 'postal_code' => '523354', 105 | 'city' => 'Oss', 106 | 'region' => 'NB', 107 | 'phone' => '24234234', 108 | 'country' => 'NL', 109 | ], 110 | ], 111 | ]; 112 | } 113 | 114 | /** 115 | * @dataProvider addressDataProvider 116 | * 117 | * @param array $addressData 118 | * @param array $expectedOutcome 119 | */ 120 | public function testGetDataWillReturnCorrectData(array $addressData, array $expectedOutcome) 121 | { 122 | $this->updateCustomerAddressRequest->initialize( 123 | [ 124 | 'transactionReference' => 123, 125 | 'billing_address' => $addressData, 126 | 'shipping_address' => $addressData, 127 | ] 128 | ); 129 | 130 | /** @noinspection PhpUnhandledExceptionInspection */ 131 | self::assertEquals( 132 | [ 133 | 'shipping_address' => $expectedOutcome, 134 | 'billing_address' => $expectedOutcome, 135 | ], 136 | $this->updateCustomerAddressRequest->getData() 137 | ); 138 | } 139 | 140 | public function testGetDataWillThrowExceptionOnMissingData() 141 | { 142 | $this->expectException(InvalidRequestException::class); 143 | 144 | $this->updateCustomerAddressRequest->getData(); 145 | } 146 | 147 | public function testSendDataWillWillSendDataToKlarnaEndPointAndReturnCorrectResponse() 148 | { 149 | $transactionReference = 'foo'; 150 | $data = ['foo' => 'bar']; 151 | $responseData = ['hello' => 'world']; 152 | 153 | $response = $this->setExpectedPatchRequest( 154 | $data, 155 | $responseData, 156 | \sprintf('%s/ordermanagement/v1/orders/%s/customer-details', self::BASE_URL, $transactionReference) 157 | ); 158 | 159 | $response->expects(self::once())->method('getStatusCode')->willReturn(204); 160 | 161 | $this->updateCustomerAddressRequest->initialize( 162 | [ 163 | 'base_url' => self::BASE_URL, 164 | 'secret' => self::SECRET, 165 | 'username' => self::USERNAME, 166 | 'transactionReference' => $transactionReference, 167 | ] 168 | ); 169 | 170 | $updateCustomerAddressResponse = $this->updateCustomerAddressRequest->sendData($data); 171 | 172 | self::assertInstanceOf(UpdateCustomerAddressResponse::class, $updateCustomerAddressResponse); 173 | self::assertSame($transactionReference, $updateCustomerAddressResponse->getTransactionReference()); 174 | self::assertSame( 175 | \array_merge($responseData, ['order_id' => $transactionReference]), 176 | $updateCustomerAddressResponse->getData() 177 | ); 178 | self::assertTrue($updateCustomerAddressResponse->isSuccessful()); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /tests/Message/UpdateCustomerAddressResponseTest.php: -------------------------------------------------------------------------------- 1 | 'oh_noes'], false, 403], 19 | [[], false, 200], 20 | [[], true, 204], 21 | ]; 22 | } 23 | 24 | public function testGetters() 25 | { 26 | $request = $this->createMock(RequestInterface::class); 27 | 28 | $responseData = ['order_id' => 'foo']; 29 | $response = new UpdateCustomerAddressResponse($request, $responseData, '403'); 30 | 31 | self::assertSame('foo', $response->getTransactionReference()); 32 | } 33 | 34 | /** 35 | * @dataProvider responseDataProvider 36 | * 37 | * @param array $responseData 38 | * @param bool $expected 39 | * @param int $reponseCode 40 | */ 41 | public function testIsSuccessfulWillReturnWhetherResponseIsSuccessfull($responseData, $expected, $reponseCode) 42 | { 43 | $request = $this->createMock(RequestInterface::class); 44 | 45 | $response = new UpdateCustomerAddressResponse($request, $responseData, $reponseCode); 46 | 47 | self::assertEquals($expected, $response->isSuccessful()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Message/UpdateMerchantReferencesRequestTest.php: -------------------------------------------------------------------------------- 1 | updateTransactionRequest = new UpdateMerchantReferencesRequest( 23 | $this->httpClient, 24 | $this->getHttpRequest() 25 | ); 26 | } 27 | 28 | public function testGetDataWillReturnCorrectData() 29 | { 30 | $this->updateTransactionRequest->initialize( 31 | [ 32 | 'merchant_reference1' => '12345', 33 | 'merchant_reference2' => 678, 34 | 'transactionReference' => self::TRANSACTION_REFERENCE, 35 | ] 36 | ); 37 | 38 | self::assertEquals( 39 | ['merchant_reference1' => '12345', 'merchant_reference2' => 678], 40 | $this->updateTransactionRequest->getData() 41 | ); 42 | } 43 | 44 | public function testSendDataWillUpdateManagementCustomerDetailsAndFailUpdatingMerchantReferences() 45 | { 46 | $inputData = ['merchant_reference1' => 'foo']; 47 | 48 | $response = $this->createMock(ResponseInterface::class); 49 | $stream = $this->createMock(StreamInterface::class); 50 | 51 | $this->httpClient->expects(self::once()) 52 | ->method('request') 53 | ->withConsecutive( 54 | [ 55 | 'PATCH', 56 | \sprintf( 57 | '%s/ordermanagement/v1/orders/%s/merchant-references', 58 | self::BASE_URL, 59 | self::TRANSACTION_REFERENCE 60 | ), 61 | \array_merge( 62 | ['Content-Type' => 'application/json'], 63 | $this->getExpectedHeaders() 64 | ), 65 | \json_encode($inputData), 66 | ] 67 | ) 68 | ->willReturn($response); 69 | 70 | $response->expects(self::once()) 71 | ->method('getBody') 72 | ->willReturn($stream); 73 | 74 | $stream->expects(self::once()) 75 | ->method('getContents') 76 | ->willReturnOnConsecutiveCalls( 77 | \json_encode(['error_code' => 'doomsday']) 78 | ); 79 | 80 | $this->updateTransactionRequest->initialize( 81 | [ 82 | 'base_url' => self::BASE_URL, 83 | 'username' => self::USERNAME, 84 | 'secret' => self::SECRET, 85 | 'transactionReference' => self::TRANSACTION_REFERENCE, 86 | ] 87 | ); 88 | 89 | self::assertFalse($this->updateTransactionRequest->sendData($inputData)->isSuccessful()); 90 | } 91 | 92 | public function testSendDataWillUpdateOrderManagementMerchantReferences() 93 | { 94 | $merchantReferencesData = ['merchant_reference1' => 'baz', 'merchant_reference2' => 'quz']; 95 | 96 | $response = $this->createMock(ResponseInterface::class); 97 | $stream = $this->createMock(StreamInterface::class); 98 | 99 | $this->httpClient->expects(self::once()) 100 | ->method('request') 101 | ->withConsecutive( 102 | [ 103 | 'PATCH', 104 | \sprintf( 105 | '%s/ordermanagement/v1/orders/%s/merchant-references', 106 | self::BASE_URL, 107 | self::TRANSACTION_REFERENCE 108 | ), 109 | \array_merge(['Content-Type' => 'application/json'], $this->getExpectedHeaders()), 110 | \json_encode($merchantReferencesData), 111 | ] 112 | ) 113 | ->willReturn($response); 114 | 115 | $response->expects(self::once()) 116 | ->method('getBody') 117 | ->willReturn($stream); 118 | 119 | $stream->expects(self::once()) 120 | ->method('getContents') 121 | ->willReturnOnConsecutiveCalls(\json_encode([])); 122 | 123 | $this->updateTransactionRequest->initialize( 124 | [ 125 | 'base_url' => self::BASE_URL, 126 | 'username' => self::USERNAME, 127 | 'secret' => self::SECRET, 128 | 'transactionReference' => self::TRANSACTION_REFERENCE, 129 | ] 130 | ); 131 | 132 | $updateTransactionResponse = $this->updateTransactionRequest->sendData($merchantReferencesData); 133 | self::assertEmpty($updateTransactionResponse->getData()); 134 | self::assertTrue($updateTransactionResponse->isSuccessful()); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/Message/UpdateTransactionRequestTest.php: -------------------------------------------------------------------------------- 1 | updateTransactionRequest = new UpdateTransactionRequest($this->httpClient, $this->getHttpRequest()); 25 | } 26 | 27 | /** 28 | * @return array 29 | */ 30 | public function merchantUrlDataProvider(): array 31 | { 32 | return [ 33 | [$this->getMinimalValidMerchantUrlData(), $this->getMinimalExpectedMerchantUrlData()], 34 | [$this->getCompleteValidMerchantUrlData(), $this->getCompleteExpectedMerchantUrlData()], 35 | ]; 36 | } 37 | 38 | public function testGetDataWillReturnCorrectData() 39 | { 40 | $this->updateTransactionRequest->initialize( 41 | [ 42 | 'amount' => '100.00', 43 | 'tax_amount' => 21, 44 | 'currency' => 'EUR', 45 | 'transactionReference' => self::TRANSACTION_REFERENCE, 46 | 'gui_minimal_confirmation' => true, 47 | 'gui_autofocus' => false, 48 | 'merchant_reference1' => '12345', 49 | 'merchant_reference2' => 678, 50 | 'purchase_country' => 'FR', 51 | ] 52 | ); 53 | $this->updateTransactionRequest->setItems([$this->getItemMock()]); 54 | 55 | self::assertEquals( 56 | [ 57 | 'order_amount' => 10000, 58 | 'order_tax_amount' => 2100, 59 | 'order_lines' => [$this->getExpectedOrderLine()], 60 | 'purchase_currency' => 'EUR', 61 | 'gui' => ['options' => ['disable_autofocus', 'minimal_confirmation']], 62 | 'merchant_reference1' => '12345', 63 | 'merchant_reference2' => 678, 64 | 'purchase_country' => 'FR', 65 | ], 66 | $this->updateTransactionRequest->getData() 67 | ); 68 | } 69 | 70 | public function testGetDataWillReturnCorrectDataForEmptyCart() 71 | { 72 | $this->updateTransactionRequest->initialize( 73 | [ 74 | 'amount' => '100.00', 75 | 'tax_amount' => 21, 76 | 'currency' => 'EUR', 77 | 'transactionReference' => self::TRANSACTION_REFERENCE, 78 | 'gui_minimal_confirmation' => true, 79 | 'gui_autofocus' => false, 80 | 'merchant_reference1' => '12345', 81 | 'merchant_reference2' => 678, 82 | 'purchase_country' => 'FR', 83 | ] 84 | ); 85 | 86 | self::assertEquals( 87 | [ 88 | 'order_amount' => 10000, 89 | 'order_tax_amount' => 2100, 90 | 'order_lines' => [], 91 | 'purchase_currency' => 'EUR', 92 | 'gui' => ['options' => ['disable_autofocus', 'minimal_confirmation']], 93 | 'merchant_reference1' => '12345', 94 | 'merchant_reference2' => 678, 95 | 'purchase_country' => 'FR', 96 | ], 97 | $this->updateTransactionRequest->getData() 98 | ); 99 | } 100 | 101 | public function testGetDataWillThrowExceptionForInvalidRequest() 102 | { 103 | $this->updateTransactionRequest->initialize([]); 104 | 105 | $this->expectException(InvalidRequestException::class); 106 | $this->updateTransactionRequest->getData(); 107 | } 108 | 109 | public function testGetDataWithAddressWillReturnCorrectData() 110 | { 111 | $organization = 'Foo inc'; 112 | $reference = 'ref'; 113 | $attention = 'quz'; 114 | $email = 'foo@bar.com'; 115 | $title = 'Mr.'; 116 | $streetAddress = 'Foo Street 1'; 117 | $streetAddress2 = 'App. 12A'; 118 | $streetName = 'Foo Street'; 119 | $houseExtension = 'C'; 120 | $streetNumber = '1'; 121 | $postalCode = '523354'; 122 | $city = 'Oss'; 123 | $region = 'NB'; 124 | $phone = '24234234'; 125 | $country = 'NL'; 126 | 127 | $shippingAddress = [ 128 | 'organization_name' => $organization, 129 | 'reference' => $reference, 130 | 'attention' => $attention, 131 | 'given_name' => 'foo', 132 | 'family_name' => 'bar', 133 | 'email' => $email, 134 | 'title' => $title, 135 | 'street_address' => $streetAddress, 136 | 'street_address2' => $streetAddress2, 137 | 'street_name' => $streetName, 138 | 'street_number' => $streetNumber, 139 | 'house_extension' => $houseExtension, 140 | 'postal_code' => $postalCode, 141 | 'city' => $city, 142 | 'region' => $region, 143 | 'phone' => $phone, 144 | 'country' => $country, 145 | ]; 146 | $billingAddress = [ 147 | 'organization_name' => $organization, 148 | 'reference' => $reference, 149 | 'attention' => $attention, 150 | 'given_name' => 'bar', 151 | 'family_name' => 'foo', 152 | 'email' => $email, 153 | 'title' => $title, 154 | 'street_address' => $streetAddress, 155 | 'street_address2' => $streetAddress2, 156 | 'street_name' => $streetName, 157 | 'street_number' => $streetNumber, 158 | 'house_extension' => $houseExtension, 159 | 'postal_code' => $postalCode, 160 | 'city' => $city, 161 | 'region' => $region, 162 | 'phone' => $phone, 163 | 'country' => $country, 164 | ]; 165 | 166 | $this->updateTransactionRequest->initialize( 167 | [ 168 | 'locale' => 'nl_NL', 169 | 'amount' => '100.00', 170 | 'tax_amount' => 21, 171 | 'currency' => 'EUR', 172 | 'transactionReference' => self::TRANSACTION_REFERENCE, 173 | 'gui_minimal_confirmation' => true, 174 | 'gui_autofocus' => false, 175 | 'merchant_reference1' => '12345', 176 | 'merchant_reference2' => 678, 177 | 'purchase_country' => 'NL', 178 | ] 179 | ); 180 | $this->updateTransactionRequest->setItems([$this->getItemMock()]); 181 | $this->updateTransactionRequest->setShippingAddress($shippingAddress); 182 | $this->updateTransactionRequest->setBillingAddress($billingAddress); 183 | 184 | self::assertEquals( 185 | [ 186 | 'locale' => 'nl-NL', 187 | 'order_amount' => 10000, 188 | 'order_tax_amount' => 2100, 189 | 'order_lines' => [$this->getExpectedOrderLine()], 190 | 'purchase_country' => 'NL', 191 | 'purchase_currency' => 'EUR', 192 | 'gui' => ['options' => ['disable_autofocus', 'minimal_confirmation']], 193 | 'merchant_reference1' => '12345', 194 | 'merchant_reference2' => 678, 195 | 'shipping_address' => $shippingAddress, 196 | 'billing_address' => $billingAddress, 197 | ], 198 | $this->updateTransactionRequest->getData() 199 | ); 200 | } 201 | 202 | public function testGetDataWithCustomerWillReturnCorrectData() 203 | { 204 | $customer = [ 205 | 'date_of_birth' => '1995-10-20', 206 | 'type' => 'organization', 207 | ]; 208 | 209 | $this->updateTransactionRequest->initialize( 210 | [ 211 | 'locale' => 'nl_NL', 212 | 'amount' => '100.00', 213 | 'tax_amount' => 21, 214 | 'currency' => 'EUR', 215 | 'transactionReference' => self::TRANSACTION_REFERENCE, 216 | 'purchase_country' => 'FR', 217 | ] 218 | ); 219 | $this->updateTransactionRequest->setItems([$this->getItemMock()]); 220 | $this->updateTransactionRequest->setCustomer($customer); 221 | 222 | self::assertEquals( 223 | [ 224 | 'locale' => 'nl-NL', 225 | 'order_amount' => 10000, 226 | 'order_tax_amount' => 2100, 227 | 'order_lines' => [$this->getExpectedOrderLine()], 228 | 'purchase_country' => 'FR', 229 | 'purchase_currency' => 'EUR', 230 | 'customer' => $customer, 231 | ], 232 | $this->updateTransactionRequest->getData() 233 | ); 234 | } 235 | 236 | /** 237 | * @dataProvider merchantUrlDataProvider 238 | * 239 | * @param array $merchantUrlData 240 | * @param array $expectedMerchantUrls 241 | */ 242 | public function testGetDataWithMerchantUrlsWillReturnCorrectData( 243 | $merchantUrlData, 244 | $expectedMerchantUrls 245 | ) { 246 | $this->updateTransactionRequest->initialize( 247 | \array_merge( 248 | [ 249 | 'amount' => '100.00', 250 | 'tax_amount' => 21, 251 | 'currency' => 'EUR', 252 | 'transactionReference' => self::TRANSACTION_REFERENCE, 253 | 'gui_minimal_confirmation' => true, 254 | 'gui_autofocus' => false, 255 | 'merchant_reference1' => '12345', 256 | 'merchant_reference2' => 678, 257 | 'purchase_country' => 'FR', 258 | 'returnUrl' => 'localhost/return', 259 | 'notifyUrl' => 'localhost/notify', 260 | 'termsUrl' => 'localhost/terms', 261 | ], 262 | $merchantUrlData 263 | ) 264 | ); 265 | $this->updateTransactionRequest->setItems([$this->getItemMock()]); 266 | 267 | self::assertEquals( 268 | [ 269 | 'order_amount' => 10000, 270 | 'order_tax_amount' => 2100, 271 | 'order_lines' => [$this->getExpectedOrderLine()], 272 | 'purchase_currency' => 'EUR', 273 | 'gui' => ['options' => ['disable_autofocus', 'minimal_confirmation']], 274 | 'merchant_reference1' => '12345', 275 | 'merchant_reference2' => '678', 276 | 'purchase_country' => 'FR', 277 | 'merchant_urls' => $expectedMerchantUrls, 278 | ], 279 | $this->updateTransactionRequest->getData() 280 | ); 281 | } 282 | 283 | public function testGetDataWithOptionsWillReturnCorrectData() 284 | { 285 | $widgetOptions = [ 286 | 'acquiring_channel' => 'foo', 287 | 'allow_separate_shipping_address' => true, 288 | 'color_button' => '#FFFFF', 289 | 'color_button_text' => '#FFFFF', 290 | 'color_checkbox' => '#FFFFF', 291 | 'color_checkbox_checkmark' => '#FFFFF', 292 | 'color_header' => '#FFFFF', 293 | 'color_link' => '#FFFFF', 294 | 'date_of_birth_mandatory' => true, 295 | 'shipping_details' => 'Delivered within 1-3 working days', 296 | 'title_mandatory' => true, 297 | 'additional_checkbox' => [ 298 | 'text' => 'Please add me to the newsletter list', 299 | 'checked' => false, 300 | 'required' => false, 301 | ], 302 | 'radius_border' => '5px', 303 | 'show_subtotal_detail' => true, 304 | 'require_validate_callback_success' => true, 305 | 'allow_global_billing_countries' => false, 306 | ]; 307 | 308 | $this->updateTransactionRequest->initialize( 309 | [ 310 | 'locale' => 'nl_NL', 311 | 'amount' => '100.00', 312 | 'tax_amount' => 21, 313 | 'currency' => 'EUR', 314 | 'transactionReference' => self::TRANSACTION_REFERENCE, 315 | 'gui_minimal_confirmation' => true, 316 | 'gui_autofocus' => false, 317 | 'merchant_reference1' => '12345', 318 | 'merchant_reference2' => 678, 319 | 'purchase_country' => 'DE', 320 | ] 321 | ); 322 | $this->updateTransactionRequest->setItems([$this->getItemMock()]); 323 | $this->updateTransactionRequest->setWidgetOptions($widgetOptions); 324 | 325 | /** @noinspection PhpUnhandledExceptionInspection */ 326 | /** @noinspection PhpUnhandledExceptionInspection */ 327 | self::assertEquals( 328 | [ 329 | 'locale' => 'nl-NL', 330 | 'order_amount' => 10000, 331 | 'order_tax_amount' => 2100, 332 | 'order_lines' => [$this->getExpectedOrderLine()], 333 | 'purchase_country' => 'DE', 334 | 'purchase_currency' => 'EUR', 335 | 'gui' => ['options' => ['disable_autofocus', 'minimal_confirmation']], 336 | 'merchant_reference1' => '12345', 337 | 'merchant_reference2' => 678, 338 | 'options' => $widgetOptions, 339 | ], 340 | $this->updateTransactionRequest->getData() 341 | ); 342 | } 343 | 344 | public function testSendDataWillCreateOrderAndReturnResponse() 345 | { 346 | $inputData = ['request-data' => 'yey?']; 347 | $responseData = []; 348 | 349 | $this->setExpectedPostRequest( 350 | $inputData, 351 | $responseData, 352 | \sprintf('%s/checkout/v3/orders/%s', self::BASE_URL, self::TRANSACTION_REFERENCE) 353 | ); 354 | 355 | $this->updateTransactionRequest->initialize( 356 | [ 357 | 'base_url' => self::BASE_URL, 358 | 'username' => self::USERNAME, 359 | 'secret' => self::SECRET, 360 | 'transactionReference' => self::TRANSACTION_REFERENCE, 361 | ] 362 | ); 363 | 364 | $updateTransactionResponse = $this->updateTransactionRequest->sendData($inputData); 365 | 366 | self::assertInstanceOf(UpdateTransactionResponse::class, $updateTransactionResponse); 367 | self::assertSame($responseData, $updateTransactionResponse->getData()); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /tests/Message/VoidRequestTest.php: -------------------------------------------------------------------------------- 1 | voidRequest = new VoidRequest($this->httpClient, $this->getHttpRequest()); 23 | } 24 | 25 | public function testGetDataWillReturnCorrectData() 26 | { 27 | $this->voidRequest->initialize(['transactionReference' => 'foo']); 28 | 29 | /** @noinspection PhpUnhandledExceptionInspection */ 30 | self::assertEquals([], $this->voidRequest->getData()); 31 | } 32 | 33 | public function testGetDataWillThrowExceptionForInvalidRequest() 34 | { 35 | $this->voidRequest->initialize([]); 36 | 37 | $this->expectException(InvalidRequestException::class); 38 | /** @noinspection PhpUnhandledExceptionInspection */ 39 | $this->voidRequest->getData(); 40 | } 41 | 42 | /** 43 | * @dataProvider voidRequestCaptureDataProvider 44 | * 45 | * @param array $captures 46 | * @param string $expectedPostRoute 47 | */ 48 | public function testSendDataWillVoidOrderAndReturnResponse(array $captures, $expectedPostRoute) 49 | { 50 | $inputData = ['request-data' => 'yey?']; 51 | $expectedData = []; 52 | 53 | $response = $this->createMock(ResponseInterface::class); 54 | $stream = $this->createMock(StreamInterface::class); 55 | 56 | $this->httpClient->expects(self::exactly(2)) 57 | ->method('request') 58 | ->withConsecutive( 59 | [ 60 | 'GET', 61 | self::BASE_URL . '/ordermanagement/v1/orders/' . self::TRANSACTION_REF, 62 | $this->getExpectedHeaders(), 63 | null, 64 | ], 65 | [ 66 | 'POST', 67 | self::BASE_URL . '/ordermanagement/v1/orders/' . self::TRANSACTION_REF . $expectedPostRoute, 68 | \array_merge(['Content-Type' => 'application/json'], $this->getExpectedHeaders()), 69 | \json_encode($inputData), 70 | ] 71 | ) 72 | ->willReturn($response); 73 | 74 | $response->method('getBody')->willReturn($stream); 75 | $stream->expects(self::exactly(2)) 76 | ->method('getContents') 77 | ->willReturnOnConsecutiveCalls( 78 | \json_encode(['captures' => $captures]), 79 | \json_encode($expectedData) 80 | ); 81 | 82 | $response->expects(self::once())->method('getStatusCode')->willReturn(204); 83 | 84 | $this->voidRequest->initialize( 85 | [ 86 | 'base_url' => self::BASE_URL, 87 | 'username' => self::USERNAME, 88 | 'secret' => self::SECRET, 89 | 'transactionReference' => self::TRANSACTION_REF, 90 | ] 91 | ); 92 | 93 | $voidResponse = $this->voidRequest->sendData($inputData); 94 | 95 | self::assertInstanceOf(VoidResponse::class, $voidResponse); 96 | self::assertSame($expectedData, $voidResponse->getData()); 97 | } 98 | 99 | /** 100 | * @return array 101 | */ 102 | public function voidRequestCaptureDataProvider(): array 103 | { 104 | return [ 105 | [[], '/cancel'], 106 | [[['capture-id' => 1]], '/release-remaining-authorization'], 107 | ]; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/Message/VoidResponseTest.php: -------------------------------------------------------------------------------- 1 | createMock(RequestInterface::class); 27 | 28 | $response = new VoidResponse($request, [], 201); 29 | 30 | self::assertSame(201, $response->getStatusCode()); 31 | } 32 | 33 | /** 34 | * @dataProvider responseCodeProvider 35 | * 36 | * @param string $responseCode 37 | * @param bool $expectedResult 38 | */ 39 | public function testIsSuccessfulWillReturnCorrectStateWithResponseCode($responseCode, $expectedResult) 40 | { 41 | $request = $this->createMock(RequestInterface::class); 42 | 43 | $captureResponse = new VoidResponse($request, [], $responseCode); 44 | 45 | self::assertEquals($expectedResult, $captureResponse->isSuccessful()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Message/WidgetOptionsTest.php: -------------------------------------------------------------------------------- 1 | 'foo', 20 | 'allow_separate_shipping_address' => true, 21 | 'color_button' => '#FFFFF', 22 | 'color_button_text' => '#FFFFF', 23 | 'color_checkbox' => '#FFFFF', 24 | 'color_checkbox_checkmark' => '#FFFFF', 25 | 'color_header' => '#FFFFF', 26 | 'color_link' => '#FFFFF', 27 | 'date_of_birth_mandatory' => true, 28 | 'shipping_details' => 'Delivered within 1-3 working days', 29 | 'title_mandatory' => true, 30 | 'additional_checkbox' => [ 31 | 'text' => 'Please add me to the newsletter list', 32 | 'checked' => false, 33 | 'required' => false, 34 | ], 35 | 'radius_border' => '5px', 36 | 'show_subtotal_detail' => true, 37 | 'require_validate_callback_success' => true, 38 | ], 39 | [ 40 | 'acquiring_channel' => 'foo', 41 | 'allow_separate_shipping_address' => true, 42 | 'color_button' => '#FFFFF', 43 | 'color_button_text' => '#FFFFF', 44 | 'color_checkbox' => '#FFFFF', 45 | 'color_checkbox_checkmark' => '#FFFFF', 46 | 'color_header' => '#FFFFF', 47 | 'color_link' => '#FFFFF', 48 | 'date_of_birth_mandatory' => true, 49 | 'shipping_details' => 'Delivered within 1-3 working days', 50 | 'title_mandatory' => true, 51 | 'additional_checkbox' => [ 52 | 'text' => 'Please add me to the newsletter list', 53 | 'checked' => false, 54 | 'required' => false, 55 | ], 56 | 'radius_border' => '5px', 57 | 'show_subtotal_detail' => true, 58 | 'require_validate_callback_success' => true, 59 | 'allow_global_billing_countries' => false, 60 | ], 61 | ], 62 | [ 63 | [], 64 | [ 65 | 'acquiring_channel' => 'eCommerce', 66 | 'allow_separate_shipping_address' => false, 67 | 'color_button' => null, 68 | 'color_button_text' => null, 69 | 'color_checkbox' => null, 70 | 'color_checkbox_checkmark' => null, 71 | 'color_header' => null, 72 | 'color_link' => null, 73 | 'date_of_birth_mandatory' => false, 74 | 'shipping_details' => null, 75 | 'title_mandatory' => false, 76 | 'additional_checkbox' => null, 77 | 'radius_border' => null, 78 | 'show_subtotal_detail' => false, 79 | 'require_validate_callback_success' => false, 80 | 'allow_global_billing_countries' => false, 81 | ], 82 | ], 83 | [ 84 | ['foo' => 'bar'], 85 | [ 86 | 'acquiring_channel' => 'eCommerce', 87 | 'allow_separate_shipping_address' => false, 88 | 'color_button' => null, 89 | 'color_button_text' => null, 90 | 'color_checkbox' => null, 91 | 'color_checkbox_checkmark' => null, 92 | 'color_header' => null, 93 | 'color_link' => null, 94 | 'date_of_birth_mandatory' => false, 95 | 'shipping_details' => null, 96 | 'title_mandatory' => false, 97 | 'additional_checkbox' => null, 98 | 'radius_border' => null, 99 | 'show_subtotal_detail' => false, 100 | 'require_validate_callback_success' => false, 101 | 'allow_global_billing_countries' => false, 102 | ], 103 | ], 104 | ]; 105 | } 106 | 107 | /** 108 | * @dataProvider dataProvider 109 | * 110 | * @param array $data 111 | * @param array $expectedOutcome 112 | */ 113 | public function testFromArrayShoulReturnArrayWithCorrectKeys($data, $expectedOutcome) 114 | { 115 | self::assertEquals($expectedOutcome, WidgetOptions::fromArray($data)->getArrayCopy()); 116 | } 117 | } 118 | --------------------------------------------------------------------------------