diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b98bbf1..28da3b5c 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## [1.2.3.0][1.2.3.0]
+
+### Added
+*   An example for `prepayment` payment method.
+*   An example for `invoice` payment method.
+*   Charge methods `getCancelledAmount` and `getTotalAmount`.
+*   Authorize method `getCancelledAmount`.
+*   Detailed `keypair` fetch.
+*   Added properties to keypair resource.
+
+### Fixed
+*   A problem with HeidelpayApiException.
+*   A problem which resulted in an error when trying to create a `customer` implicitly with a transaction when its `customerId` was set. 
+
+### Changed
+*   Replaced unreliable `Payment::cancel()` method with `Payment::cancelAmount()` which takes multiple cancellation scenarios into account.
+*   Replaced `ApiResponseCodes::API_ERROR_AUTHORIZE_ALREADY_CANCELLED` with `ApiResponseCodes::API_ERROR_ALREADY_CANCELLED`.
+*   Replaced `ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK` with `ApiResponseCodes::API_ERROR_ALREADY_CHARGED_BACK`.
+*   Add deprecation notice for `Payment::cancelAllCharges` and `Payment::cancelAuthorization`
+*   Adapted integration tests with basket to changes in API.
+*   Refactor deprecation notices.
+*   Refactored and extended unit tests.
+*   Test keypair can now be set via environment variables.
+
 ## [1.2.2.0][1.2.2.0]
 
 ### Fixed
@@ -274,3 +298,4 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
 [1.2.0.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.1.6.0..1.2.0.0
 [1.2.1.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.0.0..1.2.1.0
 [1.2.2.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.1.0..1.2.2.0
+[1.2.3.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.2.0..1.2.3.0
diff --git a/examples/Invoice/Constants.php b/examples/Invoice/Constants.php
new file mode 100644
index 00000000..82c782a9
--- /dev/null
+++ b/examples/Invoice/Constants.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * This file defines the constants needed for the Invoice example.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/examples
+ */
+
+require_once __DIR__ . '/../Constants.php';
+
+define('EXAMPLE_PATH', __DIR__);
+define('EXAMPLE_URL', EXAMPLE_BASE_FOLDER . 'Invoice');
+define('CONTROLLER_URL', EXAMPLE_URL . '/Controller.php');
diff --git a/examples/Invoice/Controller.php b/examples/Invoice/Controller.php
new file mode 100644
index 00000000..8a2aaafe
--- /dev/null
+++ b/examples/Invoice/Controller.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * This is the controller for the Invoice example.
+ * It is called when the pay button on the index page is clicked.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/examples
+ */
+
+/** Require the constants of this example */
+require_once __DIR__ . '/Constants.php';
+
+/** @noinspection PhpIncludeInspection */
+/** Require the composer autoloader file */
+require_once __DIR__ . '/../../../../autoload.php';
+
+use heidelpayPHP\examples\ExampleDebugHandler;
+use heidelpayPHP\Exceptions\HeidelpayApiException;
+use heidelpayPHP\Heidelpay;
+use heidelpayPHP\Resources\CustomerFactory;
+use heidelpayPHP\Resources\PaymentTypes\Invoice;
+
+session_start();
+session_unset();
+
+$clientMessage = 'Something went wrong. Please try again later.';
+$merchantMessage = 'Something went wrong. Please try again later.';
+
+function redirect($url, $merchantMessage = '', $clientMessage = '')
+{
+    $_SESSION['merchantMessage'] = $merchantMessage;
+    $_SESSION['clientMessage']   = $clientMessage;
+    header('Location: ' . $url);
+    die();
+}
+
+// Catch API errors, write the message to your log and show the ClientMessage to the client.
+try {
+    // Create a heidelpay object using your private key and register a debug handler if you want to.
+    $heidelpay = new Heidelpay(HEIDELPAY_PHP_PAYMENT_API_PRIVATE_KEY);
+    $heidelpay->setDebugMode(true)->setDebugHandler(new ExampleDebugHandler());
+
+    /** @var Invoice $invoice */
+    $invoice = $heidelpay->createPaymentType(new Invoice());
+
+    $customer = CustomerFactory::createCustomer('Max', 'Mustermann');
+    $orderId = str_replace(['0.', ' '], '', microtime(false));
+
+    $transaction = $invoice->charge(12.99, 'EUR', CONTROLLER_URL, $customer, $orderId);
+
+    // You'll need to remember the shortId to show it on the success or failure page
+    $_SESSION['ShortId'] = $transaction->getShortId();
+
+    // Redirect to the success or failure page depending on the state of the transaction
+    $payment = $transaction->getPayment();
+
+    if ($payment->isPending()) {
+        // In case of authorization this is normal since you will later charge the payment.
+        // You can create the order with status pending payment and show a success page to the customer if you want.
+
+        // In cases of redirection to an external service (e.g. 3D secure, PayPal, etc) it sometimes takes time for
+        // the payment to update it's status. In this case it might be pending at first and change to cancel or success later.
+        // Use the webhooks feature to stay informed about changes of the payment (e.g. cancel, success)
+        // then you can cancel the order later or mark it paid as soon as the event is triggered.
+
+        // In any case, the payment is not done when the payment is pending and you should ship until it changes to success.
+        redirect(PENDING_URL);
+    }
+    // If the payment is neither success nor pending something went wrong.
+    // In this case do not create the order.
+    // Redirect to an error page in your shop and show an message if you want.
+
+    // Check the result message of the transaction to find out what went wrong.
+    $merchantMessage = $transaction->getMessage()->getCustomer();
+} catch (HeidelpayApiException $e) {
+    $merchantMessage = $e->getMerchantMessage();
+    $clientMessage = $e->getClientMessage();
+} catch (RuntimeException $e) {
+    $merchantMessage = $e->getMessage();
+}
+redirect(FAILURE_URL, $merchantMessage, $clientMessage);
diff --git a/examples/Invoice/index.php b/examples/Invoice/index.php
new file mode 100644
index 00000000..d3c1a749
--- /dev/null
+++ b/examples/Invoice/index.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * This file provides an example implementation of the Invoice payment type.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/examples
+ */
+
+/** Require the constants of this example */
+require_once __DIR__ . '/Constants.php';
+
+/** @noinspection PhpIncludeInspection */
+
+/** Require the composer autoloader file */
+require_once __DIR__ . '/../../../../autoload.php';
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>
+        Heidelpay UI Examples
+    </title>
+    <script src="https://code.jquery.com/jquery-3.1.1.min.js"
+            integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
+
+    <link rel="stylesheet" href="https://static.heidelpay.com/v1/heidelpay.css" />
+</head>
+
+<body style="margin: 70px 70px 0;">
+
+<form id="payment-form" action="<?php echo CONTROLLER_URL; ?>" class="heidelpayUI form" novalidate>
+    <button class="heidelpayUI primary button fluid" id="submit-button" type="submit">Pay</button>
+</form>
+
+</body>
+</html>
diff --git a/examples/Prepayment/Constants.php b/examples/Prepayment/Constants.php
new file mode 100644
index 00000000..917dff7e
--- /dev/null
+++ b/examples/Prepayment/Constants.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * This file defines the constants needed for the Prepayment example.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/examples
+ */
+
+require_once __DIR__ . '/../Constants.php';
+
+define('EXAMPLE_PATH', __DIR__);
+define('EXAMPLE_URL', EXAMPLE_BASE_FOLDER . 'Prepayment');
+define('CONTROLLER_URL', EXAMPLE_URL . '/Controller.php');
diff --git a/examples/Prepayment/Controller.php b/examples/Prepayment/Controller.php
new file mode 100644
index 00000000..b33ad4d4
--- /dev/null
+++ b/examples/Prepayment/Controller.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * This is the controller for the Prepayment example.
+ * It is called when the pay button on the index page is clicked.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/examples
+ */
+
+/** Require the constants of this example */
+require_once __DIR__ . '/Constants.php';
+
+/** @noinspection PhpIncludeInspection */
+/** Require the composer autoloader file */
+require_once __DIR__ . '/../../../../autoload.php';
+
+use heidelpayPHP\examples\ExampleDebugHandler;
+use heidelpayPHP\Exceptions\HeidelpayApiException;
+use heidelpayPHP\Heidelpay;
+use heidelpayPHP\Resources\CustomerFactory;
+use heidelpayPHP\Resources\PaymentTypes\Prepayment;
+
+session_start();
+session_unset();
+
+$clientMessage = 'Something went wrong. Please try again later.';
+$merchantMessage = 'Something went wrong. Please try again later.';
+
+function redirect($url, $merchantMessage = '', $clientMessage = '')
+{
+    $_SESSION['merchantMessage'] = $merchantMessage;
+    $_SESSION['clientMessage']   = $clientMessage;
+    header('Location: ' . $url);
+    die();
+}
+
+// Catch API errors, write the message to your log and show the ClientMessage to the client.
+try {
+    // Create a heidelpay object using your private key and register a debug handler if you want to.
+    $heidelpay = new Heidelpay(HEIDELPAY_PHP_PAYMENT_API_PRIVATE_KEY);
+    $heidelpay->setDebugMode(true)->setDebugHandler(new ExampleDebugHandler());
+
+    /** @var Prepayment $prepayment */
+    $prepayment = $heidelpay->createPaymentType(new Prepayment());
+
+    $customer = CustomerFactory::createCustomer('Max', 'Mustermann');
+    $orderId = str_replace(['0.', ' '], '', microtime(false));
+
+    $transaction = $prepayment->charge(12.99, 'EUR', CONTROLLER_URL, $customer, $orderId);
+
+    // You'll need to remember the shortId to show it on the success or failure page
+    $_SESSION['ShortId'] = $transaction->getShortId();
+
+    // Redirect to the success or failure page depending on the state of the transaction
+    $payment = $transaction->getPayment();
+
+    if ($payment->isPending()) {
+        // In case of authorization this is normal since you will later charge the payment.
+        // You can create the order with status pending payment and show a success page to the customer if you want.
+
+        // In cases of redirection to an external service (e.g. 3D secure, PayPal, etc) it sometimes takes time for
+        // the payment to update it's status. In this case it might be pending at first and change to cancel or success later.
+        // Use the webhooks feature to stay informed about changes of the payment (e.g. cancel, success)
+        // then you can cancel the order later or mark it paid as soon as the event is triggered.
+
+        // In any case, the payment is not done when the payment is pending and you should ship until it changes to success.
+        redirect(PENDING_URL);
+    }
+    // If the payment is neither success nor pending something went wrong.
+    // In this case do not create the order.
+    // Redirect to an error page in your shop and show an message if you want.
+
+    // Check the result message of the transaction to find out what went wrong.
+    $merchantMessage = $transaction->getMessage()->getCustomer();
+} catch (HeidelpayApiException $e) {
+    $merchantMessage = $e->getMerchantMessage();
+    $clientMessage = $e->getClientMessage();
+} catch (RuntimeException $e) {
+    $merchantMessage = $e->getMessage();
+}
+redirect(FAILURE_URL, $merchantMessage, $clientMessage);
diff --git a/examples/Prepayment/index.php b/examples/Prepayment/index.php
new file mode 100644
index 00000000..dedf71df
--- /dev/null
+++ b/examples/Prepayment/index.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * This file provides an example implementation of the Prepayment payment type.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/examples
+ */
+
+/** Require the constants of this example */
+require_once __DIR__ . '/Constants.php';
+
+/** @noinspection PhpIncludeInspection */
+
+/** Require the composer autoloader file */
+require_once __DIR__ . '/../../../../autoload.php';
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>
+        Heidelpay UI Examples
+    </title>
+    <script src="https://code.jquery.com/jquery-3.1.1.min.js"
+            integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
+
+    <link rel="stylesheet" href="https://static.heidelpay.com/v1/heidelpay.css" />
+</head>
+
+<body style="margin: 70px 70px 0;">
+
+<form id="payment-form" action="<?php echo CONTROLLER_URL; ?>" class="heidelpayUI form" novalidate>
+    <button class="heidelpayUI primary button fluid" id="submit-button" type="submit">Pay</button>
+</form>
+
+</body>
+</html>
diff --git a/examples/_enableExamples.php b/examples/_enableExamples.php
index 44337ee5..03980683 100755
--- a/examples/_enableExamples.php
+++ b/examples/_enableExamples.php
@@ -39,8 +39,6 @@
  * Default Values are:
  * Private key: s-priv-2a102ZMq3gV4I3zJ888J7RR6u75oqK3n
  * Public key:  s-pub-2a10ifVINFAjpQJ9qW8jBe5OJPBx6Gxa
- *
- * Please keep in mind,
  */
 define('HEIDELPAY_PHP_PAYMENT_API_PRIVATE_KEY', 's-priv-2a102ZMq3gV4I3zJ888J7RR6u75oqK3n');
 define('HEIDELPAY_PHP_PAYMENT_API_PUBLIC_KEY', 's-pub-2a10ifVINFAjpQJ9qW8jBe5OJPBx6Gxa');
diff --git a/examples/index.php b/examples/index.php
index 6cf810b0..3b6cfd4c 100755
--- a/examples/index.php
+++ b/examples/index.php
@@ -140,6 +140,30 @@
                         Try
                     </div>
                 </div>
+                <div class="card olive">
+                    <div class="content">
+                        <div class="header">
+                            Prepayment
+                        </div>
+                        <div class="description">
+                        </div>
+                    </div>
+                    <div class="ui bottom attached green button" onclick="location.href='Prepayment/';">
+                        Try
+                    </div>
+                </div>
+                <div class="card olive">
+                    <div class="content">
+                        <div class="header">
+                            Invoice
+                        </div>
+                        <div class="description">
+                        </div>
+                    </div>
+                    <div class="ui bottom attached green button" onclick="location.href='Invoice/';">
+                        Try
+                    </div>
+                </div>
                 <div class="card olive">
                     <div class="content">
                         <div class="header">
@@ -221,7 +245,7 @@
                 <div class="card olive">
                     <div class="content">
                         <div class="header">
-                            Flexipay (PIS)
+                            Flexipay direct (PIS)
                         </div>
                         <div class="description">
                         </div>
diff --git a/src/Constants/ApiResponseCodes.php b/src/Constants/ApiResponseCodes.php
index 8a128895..5ec64324 100755
--- a/src/Constants/ApiResponseCodes.php
+++ b/src/Constants/ApiResponseCodes.php
@@ -44,8 +44,18 @@ class ApiResponseCodes
     const API_ERROR_IVF_REQUIRES_BASKET                         = 'API.330.100.023';
     const API_ERROR_ADDRESSES_DO_NOT_MATCH                      = 'API.330.100.106';
     const API_ERROR_CURRENCY_IS_NOT_SUPPORTED                   = 'API.330.100.202';
+    /**
+     * @deprecated since 1.2.3.0
+     * @see ApiResponseCodes::API_ERROR_ALREADY_CANCELLED
+     */
     const API_ERROR_AUTHORIZE_ALREADY_CANCELLED                 = 'API.340.100.014';
+    const API_ERROR_ALREADY_CANCELLED                           = 'API.340.100.014';
+    /**
+     * @deprecated since 1.2.3.0
+     * @see ApiResponseCodes::API_ERROR_ALREADY_CHARGED_BACK
+     */
     const API_ERROR_CHARGE_ALREADY_CHARGED_BACK                 = 'API.340.100.015';
+    const API_ERROR_ALREADY_CHARGED_BACK                        = 'API.340.100.015';
     const API_ERROR_ALREADY_CHARGED                             = 'API.340.100.018';
     const API_ERROR_CANCEL_REASON_CODE_IS_MISSING               = 'API.340.100.024';
     const API_ERROR_AMOUNT_IS_MISSING                           = 'API.340.200.130';
diff --git a/src/Exceptions/HeidelpayApiException.php b/src/Exceptions/HeidelpayApiException.php
index 38f6e07b..9eb52118 100755
--- a/src/Exceptions/HeidelpayApiException.php
+++ b/src/Exceptions/HeidelpayApiException.php
@@ -45,13 +45,13 @@ class HeidelpayApiException extends Exception
      * @param string $code
      * @param string $errorId
      */
-    public function __construct($merchantMessage = '', $clientMessage = '', $code = 'No error code provided', $errorId = 'No error id provided')
+    public function __construct($merchantMessage = '', $clientMessage = '', $code = null, $errorId = null)
     {
         $merchantMessage = empty($merchantMessage) ? static::MESSAGE : $merchantMessage;
         $this->clientMessage = empty($clientMessage) ? static::CLIENT_MESSAGE : $clientMessage;
         parent::__construct($merchantMessage);
-        $this->code = $code;
-        $this->errorId = $errorId;
+        $this->code = empty($code) ? 'No error code provided' : $code;
+        $this->errorId = empty($errorId) ? 'No error id provided' : $errorId;
     }
 
     /**
diff --git a/src/Heidelpay.php b/src/Heidelpay.php
index 7ba4c7fa..e563f33c 100755
--- a/src/Heidelpay.php
+++ b/src/Heidelpay.php
@@ -56,7 +56,7 @@ class Heidelpay implements HeidelpayParentInterface
     const BASE_URL = 'api.heidelpay.com';
     const API_VERSION = 'v1';
     const SDK_TYPE = 'HeidelpayPHP';
-    const SDK_VERSION = '1.2.2.0';
+    const SDK_VERSION = '1.2.3.0';
 
     /** @var string $key */
     private $key;
@@ -396,14 +396,16 @@ public function fetchPaymentByOrderId($orderId): Payment
     /**
      * Read and return the public key and configured payment types from API.
      *
+     * @param bool $detailed If this flag is set detailed information are fetched.
+     *
      * @return Keypair The Keypair object composed of the data returned by the API.
      *
      * @throws HeidelpayApiException A HeidelpayApiException is thrown if there is an error returned on API-request.
      * @throws RuntimeException      A RuntimeException is thrown when there is a error while using the SDK.
      */
-    public function fetchKeypair(): AbstractHeidelpayResource
+    public function fetchKeypair($detailed = false): AbstractHeidelpayResource
     {
-        return $this->resourceService->fetchKeypair();
+        return $this->resourceService->fetchKeypair($detailed);
     }
 
     //</editor-fold>
diff --git a/src/Resources/AbstractHeidelpayResource.php b/src/Resources/AbstractHeidelpayResource.php
index 909b6987..1a743056 100755
--- a/src/Resources/AbstractHeidelpayResource.php
+++ b/src/Resources/AbstractHeidelpayResource.php
@@ -24,7 +24,6 @@
  */
 namespace heidelpayPHP\Resources;
 
-use function count;
 use DateTime;
 use heidelpayPHP\Adapter\HttpAdapterInterface;
 use heidelpayPHP\Exceptions\HeidelpayApiException;
@@ -32,13 +31,14 @@
 use heidelpayPHP\Interfaces\HeidelpayParentInterface;
 use heidelpayPHP\Services\ResourceNameService;
 use heidelpayPHP\Services\ResourceService;
-use function is_array;
-use function is_callable;
-use function is_object;
 use ReflectionException;
 use ReflectionProperty;
 use RuntimeException;
 use stdClass;
+use function count;
+use function is_array;
+use function is_callable;
+use function is_object;
 
 abstract class AbstractHeidelpayResource implements HeidelpayParentInterface
 {
@@ -54,15 +54,13 @@ abstract class AbstractHeidelpayResource implements HeidelpayParentInterface
     //<editor-fold desc="Getters/Setters">
 
     /**
-     * {@inheritDoc}
+     * Returns the id of this resource.
+     *
+     * @return string|null
      */
     public function getId()
     {
-        $resourceId = $this->id;
-        if ($resourceId === null) {
-            $resourceId = $this->getExternalId();
-        }
-        return $resourceId;
+        return $this->id;
     }
 
     /**
@@ -145,8 +143,12 @@ public function getHeidelpayObject(): Heidelpay
     public function getUri($appendId = true): string
     {
         $uri = [rtrim($this->getParentResource()->getUri(), '/'), $this->getResourcePath()];
-        if ($appendId && $this->getId() !== null) {
-            $uri[] = $this->getId();
+        if ($appendId) {
+            if ($this->getId() !== null) {
+                $uri[] = $this->getId();
+            } elseif ($this->getExternalId() !== null) {
+                $uri[] = $this->getExternalId();
+            }
         }
 
         $uri[] = '';
@@ -248,7 +250,7 @@ private function getResourceService(): ResourceService
     }
 
     /**
-     * Fetches the Resource if necessary.
+     * Fetches the Resource if it has not been fetched yet and the id is set.
      *
      * @param AbstractHeidelpayResource $resource
      *
diff --git a/src/Resources/Basket.php b/src/Resources/Basket.php
index 83e6a359..bfd65ee8 100755
--- a/src/Resources/Basket.php
+++ b/src/Resources/Basket.php
@@ -24,10 +24,10 @@
  */
 namespace heidelpayPHP\Resources;
 
-use function count;
 use heidelpayPHP\Adapter\HttpAdapterInterface;
 use heidelpayPHP\Resources\EmbeddedResources\BasketItem;
 use stdClass;
+use function count;
 
 class Basket extends AbstractHeidelpayResource
 {
@@ -89,14 +89,15 @@ public function getAmountTotalGross(): float
      */
     public function setAmountTotalGross(float $amountTotalGross): Basket
     {
-        $this->amountTotalGross = $amountTotalGross;
+        $this->amountTotalGross = round($amountTotalGross, 4);
         return $this;
     }
 
     /**
      * @return float
      *
-     * @deprecated since 1.2.0.0 Please use getAmountTotalGross instead.
+     * @deprecated since 1.2.0.0
+     * @see Basket::getAmountTotalGross()
      */
     public function getAmountTotal(): float
     {
@@ -108,7 +109,8 @@ public function getAmountTotal(): float
      *
      * @return Basket
      *
-     * @deprecated since 1.2.0.0 Please use setAmountTotalGross instead.
+     * @deprecated since 1.2.0.0
+     * @see Basket::setAmountTotalGross()
      */
     public function setAmountTotal(float $amountTotal): Basket
     {
@@ -130,7 +132,7 @@ public function getAmountTotalDiscount(): float
      */
     public function setAmountTotalDiscount(float $amountTotalDiscount): Basket
     {
-        $this->amountTotalDiscount = $amountTotalDiscount;
+        $this->amountTotalDiscount = round($amountTotalDiscount, 4);
         return $this;
     }
 
@@ -149,7 +151,7 @@ public function getAmountTotalVat(): float
      */
     public function setAmountTotalVat(float $amountTotalVat): Basket
     {
-        $this->amountTotalVat = $amountTotalVat;
+        $this->amountTotalVat = round($amountTotalVat, 4);
         return $this;
     }
 
diff --git a/src/Resources/Customer.php b/src/Resources/Customer.php
index 734fa906..f22f85bb 100755
--- a/src/Resources/Customer.php
+++ b/src/Resources/Customer.php
@@ -28,8 +28,8 @@
 use heidelpayPHP\Constants\Salutations;
 use heidelpayPHP\Resources\EmbeddedResources\Address;
 use heidelpayPHP\Resources\EmbeddedResources\CompanyInfo;
-use function in_array;
 use stdClass;
+use function in_array;
 
 class Customer extends AbstractHeidelpayResource
 {
@@ -75,7 +75,10 @@ class Customer extends AbstractHeidelpayResource
      * @param string|null $firstname
      * @param string|null $lastname
      *
-     * @deprecated since Version 1.1.5.0 use CustomerFactory::createCustomer(...) in the future.
+     * @deprecated since Version 1.1.5.0
+     * @see CustomerFactory::createCustomer()
+     * @see CustomerFactory::createNotRegisteredB2bCustomer()
+     * @see CustomerFactory::createRegisteredB2bCustomer()
      */
     public function __construct(string $firstname = null, string $lastname = null)
     {
diff --git a/src/Resources/EmbeddedResources/BasketItem.php b/src/Resources/EmbeddedResources/BasketItem.php
index d24ef682..8834bfca 100755
--- a/src/Resources/EmbeddedResources/BasketItem.php
+++ b/src/Resources/EmbeddedResources/BasketItem.php
@@ -142,7 +142,7 @@ public function getVat(): float
      */
     public function setVat(float $vat): BasketItem
     {
-        $this->vat = $vat;
+        $this->vat = round($vat, 4);
         return $this;
     }
 
@@ -161,7 +161,7 @@ public function getAmountDiscount(): float
      */
     public function setAmountDiscount(float $amountDiscount): BasketItem
     {
-        $this->amountDiscount = $amountDiscount;
+        $this->amountDiscount = round($amountDiscount, 4);
         return $this;
     }
 
@@ -180,7 +180,7 @@ public function getAmountGross(): float
      */
     public function setAmountGross(float $amountGross): BasketItem
     {
-        $this->amountGross = $amountGross;
+        $this->amountGross = round($amountGross, 4);
         return $this;
     }
 
@@ -199,7 +199,7 @@ public function getAmountVat(): float
      */
     public function setAmountVat(float $amountVat): BasketItem
     {
-        $this->amountVat = $amountVat;
+        $this->amountVat = round($amountVat, 4);
         return $this;
     }
 
@@ -218,7 +218,7 @@ public function getAmountPerUnit(): float
      */
     public function setAmountPerUnit(float $amountPerUnit): BasketItem
     {
-        $this->amountPerUnit = $amountPerUnit;
+        $this->amountPerUnit = round($amountPerUnit, 4);
         return $this;
     }
 
@@ -237,7 +237,7 @@ public function getAmountNet(): float
      */
     public function setAmountNet(float $amountNet): BasketItem
     {
-        $this->amountNet = $amountNet;
+        $this->amountNet = round($amountNet, 4);
         return $this;
     }
 
diff --git a/src/Resources/Keypair.php b/src/Resources/Keypair.php
index 993c0b4e..1775e7b5 100755
--- a/src/Resources/Keypair.php
+++ b/src/Resources/Keypair.php
@@ -24,6 +24,9 @@
  */
 namespace heidelpayPHP\Resources;
 
+use heidelpayPHP\Adapter\HttpAdapterInterface;
+use stdClass;
+
 class Keypair extends AbstractHeidelpayResource
 {
     /** @var string $publicKey */
@@ -32,8 +35,34 @@ class Keypair extends AbstractHeidelpayResource
     /** @var string $privateKey */
     private $privateKey;
 
-    /** @var array $availablePaymentTypes */
-    private $availablePaymentTypes = [];
+    /** @var bool $detailed */
+    private $detailed = false;
+
+    /** @var array $paymentTypes */
+    private $paymentTypes = [];
+
+    /** @var string $secureLevel */
+    private $secureLevel;
+
+    /** @var string $alias */
+    private $alias;
+
+    /** @var string $merchantName */
+    private $merchantName;
+
+    /** @var string $merchantAddress */
+    private $merchantAddress;
+
+    /**
+     * Credentials on File / Card on File
+     * If true the credentials are stored for future transactions.
+     *
+     * @var bool|null $cof
+     */
+    private $cof;
+
+    /** @var bool $validateBasket */
+    private $validateBasket;
 
     //<editor-fold desc="Getters/Setters">
 
@@ -69,12 +98,28 @@ protected function setPrivateKey(string $privateKey)
         $this->privateKey = $privateKey;
     }
 
+    /**
+     * @return array
+     */
+    public function getPaymentTypes(): array
+    {
+        return $this->paymentTypes;
+    }
+
+    /**
+     * @param array $paymentTypes
+     */
+    protected function setPaymentTypes(array $paymentTypes)
+    {
+        $this->paymentTypes = $paymentTypes;
+    }
+
     /**
      * @return array
      */
     public function getAvailablePaymentTypes(): array
     {
-        return $this->availablePaymentTypes;
+        return $this->getPaymentTypes();
     }
 
     /**
@@ -82,7 +127,173 @@ public function getAvailablePaymentTypes(): array
      */
     protected function setAvailablePaymentTypes(array $paymentTypes)
     {
-        $this->availablePaymentTypes = $paymentTypes;
+        $this->setPaymentTypes($paymentTypes);
+    }
+
+    /**
+     * @return string
+     */
+    public function getSecureLevel(): string
+    {
+        return $this->secureLevel ?: '';
+    }
+
+    /**
+     * @param string|null $secureLevel
+     *
+     * @return Keypair
+     */
+    protected function setSecureLevel($secureLevel): Keypair
+    {
+        $this->secureLevel = $secureLevel;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getAlias(): string
+    {
+        return $this->alias ?: '';
+    }
+
+    /**
+     * @param string|null $alias
+     *
+     * @return Keypair
+     */
+    protected function setAlias($alias): Keypair
+    {
+        $this->alias = $alias;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMerchantName(): string
+    {
+        return $this->merchantName ?: '';
+    }
+
+    /**
+     * @param string|null $merchantName
+     *
+     * @return Keypair
+     */
+    protected function setMerchantName($merchantName): Keypair
+    {
+        $this->merchantName = $merchantName;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMerchantAddress(): string
+    {
+        return $this->merchantAddress ?: '';
+    }
+
+    /**
+     * @param string|null $merchantAddress
+     *
+     * @return Keypair
+     */
+    protected function setMerchantAddress($merchantAddress): Keypair
+    {
+        $this->merchantAddress = $merchantAddress;
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isDetailed(): bool
+    {
+        return $this->detailed;
+    }
+
+    /**
+     * @param bool $detailed
+     *
+     * @return Keypair
+     */
+    public function setDetailed(bool $detailed): Keypair
+    {
+        $this->detailed = $detailed;
+        return $this;
+    }
+
+    /**
+     * Returns true if Credentials are stored for later transactions.
+     *
+     * @return bool|null
+     */
+    public function isCof()
+    {
+        return $this->cof;
+    }
+
+    /**
+     * @param bool $cof
+     *
+     * @return Keypair
+     */
+    protected function setCof(bool $cof): Keypair
+    {
+        $this->cof = $cof;
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isValidateBasket(): bool
+    {
+        return $this->validateBasket;
+    }
+
+    /**
+     * @param bool $validateBasket
+     *
+     * @return Keypair
+     */
+    protected function setValidateBasket(bool $validateBasket): Keypair
+    {
+        $this->validateBasket = $validateBasket;
+        return $this;
+    }
+
+    //</editor-fold>
+
+    //<editor-fold desc="Overridable Methods">
+
+    /**
+     * @inheritDoc
+     */
+    public function handleResponse(stdClass $response, $method = HttpAdapterInterface::REQUEST_GET)
+    {
+        parent::handleResponse($response, $method);
+
+        $paymentTypes = [];
+        if (isset($response->paymentTypes)) {
+            $paymentTypes = $response->paymentTypes;
+        } elseif (isset($response->availablePaymentTypes)) {
+            $paymentTypes = $response->availablePaymentTypes;
+        }
+
+        foreach ($paymentTypes as $paymentType) {
+            $this->paymentTypes[] = $paymentType;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected function getResourcePath(): string
+    {
+        return parent::getResourcePath() . ($this->isDetailed() ? '/types' : '');
     }
 
     //</editor-fold>
diff --git a/src/Resources/Payment.php b/src/Resources/Payment.php
index 3766cb04..c9227339 100755
--- a/src/Resources/Payment.php
+++ b/src/Resources/Payment.php
@@ -26,6 +26,7 @@
 
 use heidelpayPHP\Adapter\HttpAdapterInterface;
 use heidelpayPHP\Constants\ApiResponseCodes;
+use heidelpayPHP\Constants\CancelReasonCodes;
 use heidelpayPHP\Constants\IdStrings;
 use heidelpayPHP\Constants\TransactionTypes;
 use heidelpayPHP\Exceptions\HeidelpayApiException;
@@ -41,9 +42,9 @@
 use heidelpayPHP\Services\IdService;
 use heidelpayPHP\Traits\HasOrderId;
 use heidelpayPHP\Traits\HasPaymentState;
-use function is_string;
 use RuntimeException;
 use stdClass;
+use function is_string;
 
 class Payment extends AbstractHeidelpayResource
 {
@@ -628,41 +629,112 @@ public function getExternalId()
      * If no amount is given a full cancel will be performed i. e. all Charges and Authorizations will be cancelled.
      *
      * @param float|null $amount The amount to canceled.
+     * @param string     $reason
      *
-     * @return Cancellation The resulting Cancellation object.
-     *                      If more then one cancellation is performed the last one will be returned.
+     * @return Cancellation|null The resulting Cancellation object.
+     *                           If more then one cancellation is performed the last one will be returned.
      *
      * @throws HeidelpayApiException A HeidelpayApiException is thrown if there is an error returned on API-request.
      * @throws RuntimeException      A RuntimeException is thrown when there is a error while using the SDK.
+     *
+     * @deprecated since 1.2.3.0
+     * @see Payment::cancelAmount()
      */
-    public function cancel($amount = null): Cancellation
+    public function cancel($amount = null, $reason = CancelReasonCodes::REASON_CODE_CANCEL)
     {
-        list($chargeCancels, $chargeExceptions) = $this->cancelAllCharges();
-        list($authCancel, $authException) = $this->cancelAuthorization($amount);
+        $cancellations = $this->cancelAmount($amount, $reason);
+
+        if (count($cancellations) > 0) {
+            return $cancellations[0];
+        }
 
-        $cancels = array_merge($chargeCancels, $authCancel);
-        $exceptions = array_merge($chargeExceptions, $authException);
+        throw new RuntimeException('This Payment could not be cancelled.');
+    }
 
-        if (isset($cancels[0]) && $cancels[0] instanceof Cancellation) {
-            return $cancels[0];
+    /**
+     * Performs a Cancellation transaction on the Payment.
+     * If no amount is given a full cancel will be performed i. e. all Charges and Authorizations will be cancelled.
+     *
+     * @param float|null $totalCancelAmount The amount to canceled.
+     * @param string     $reason
+     *
+     * @return Cancellation[] An array holding all Cancellation objects created with this cancel call.
+     *
+     * @throws HeidelpayApiException A HeidelpayApiException is thrown if there is an error returned on API-request.
+     * @throws RuntimeException      A RuntimeException is thrown when there is a error while using the SDK.
+     */
+    public function cancelAmount($totalCancelAmount = null, $reason = CancelReasonCodes::REASON_CODE_CANCEL): array
+    {
+        $charges = $this->charges;
+        $remainingAmountToCancel = $totalCancelAmount;
+
+        $cancelWholePayment = $remainingAmountToCancel === null;
+        $cancellations = [];
+        $cancellation = null;
+
+        if ($cancelWholePayment || $remainingAmountToCancel > 0.0) {
+            $cancellation = $this->cancelAuthorizationAmount($remainingAmountToCancel);
+
+            if ($cancellation instanceof Cancellation) {
+                $cancellations[] = $cancellation;
+                if (!$cancelWholePayment) {
+                    $remainingAmountToCancel -= $cancellation->getAmount();
+                }
+                $cancellation = null;
+            }
         }
 
-        // throw the last exception if no cancellation has been created
-        if (isset($exceptions[0]) && $exceptions[0] instanceof HeidelpayApiException) {
-            throw $exceptions[0];
+        if (!$cancelWholePayment && $remainingAmountToCancel <= 0.0) {
+            return $cancellations;
         }
 
-        throw new RuntimeException('This Payment could not be cancelled.');
+        /** @var Charge $charge */
+        foreach ($charges as $charge) {
+            $cancelAmount = null;
+            if (!$cancelWholePayment && $remainingAmountToCancel <= $charge->getTotalAmount()) {
+                $cancelAmount = $remainingAmountToCancel;
+            }
+
+            try {
+                $cancellation = $charge->cancel($cancelAmount, $reason);
+            } catch (HeidelpayApiException $e) {
+                $allowedErrors = [
+                    ApiResponseCodes::API_ERROR_ALREADY_CANCELLED,
+                    ApiResponseCodes::API_ERROR_ALREADY_CHARGED_BACK
+                ];
+
+                if (!in_array($e->getCode(), $allowedErrors, true)) {
+                    throw $e;
+                }
+                continue;
+            }
+
+            if ($cancellation instanceof Cancellation) {
+                $cancellations[] = $cancellation;
+                if (!$cancelWholePayment) {
+                    $remainingAmountToCancel -= $cancellation->getAmount();
+                }
+                $cancellation = null;
+            }
+
+            if (!$cancelWholePayment && $remainingAmountToCancel <= 0) {
+                break;
+            }
+        }
+
+        return $cancellations;
     }
 
     /**
-     * Cancels all charges of the payment and returns an array of the cancellations and already charged exceptions that
-     * occur.
+     * Cancels all charges of the payment and returns an array of the cancellations and exceptions that occur.
      *
      * @return array
      *
      * @throws HeidelpayApiException
      * @throws RuntimeException
+     *
+     * @deprecated since 1.2.3.0
+     * @see Payment::cancelAmount()
      */
     public function cancelAllCharges(): array
     {
@@ -674,13 +746,11 @@ public function cancelAllCharges(): array
             try {
                 $cancels[] = $charge->cancel();
             } catch (HeidelpayApiException $e) {
-                if (!in_array($e->getCode(),
-                              [
-                                  ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK,
-                                  ApiResponseCodes::API_ERROR_AUTHORIZE_ALREADY_CANCELLED
-                              ],
-                      true
-                )) {
+                $allowedErrors = [
+                    ApiResponseCodes::API_ERROR_ALREADY_CHARGED_BACK,
+                    ApiResponseCodes::API_ERROR_ALREADY_CANCELLED,
+                ];
+                if (!in_array($e->getCode(), $allowedErrors, true)) {
                     throw $e;
                 }
                 $exceptions[] = $e;
@@ -696,24 +766,62 @@ public function cancelAllCharges(): array
      *
      * @throws HeidelpayApiException
      * @throws RuntimeException
+     *
+     * @deprecated since 1.2.3.0
+     * @see Payment::cancelAuthorizationAmount()
      */
     public function cancelAuthorization($amount = null): array
     {
         $cancels = [];
-        $exceptions = [];
+        $cancel = $this->cancelAuthorizationAmount($amount);
+
+        if ($cancel instanceof Cancellation) {
+            $cancels[] = $cancel;
+        }
+
+        return array($cancels, []);
+    }
+
+    /**
+     * Cancel the given amount of the payments authorization.
+     *
+     * @param float|null $amount The amount to be cancelled. If null the remaining uncharged amount of the authorization
+     *                           will be cancelled completely. If it exceeds the remaining uncharged amount the
+     *                           cancellation will only cancel the remaining uncharged amount.
+     *
+     * @return Cancellation|null
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function cancelAuthorizationAmount($amount = null)
+    {
+        $cancellation = null;
+        $completeCancel = $amount === null;
+
+        $authorize = $this->getAuthorization();
+        if ($authorize !== null) {
+            $cancelAmount = null;
+            if (!$completeCancel) {
+                $remainingAuthorized = $this->getAmount()->getRemaining();
+                $cancelAmount = $amount > $remainingAuthorized ? $remainingAuthorized : $amount;
+            }
 
-        $authorization = $this->getAuthorization();
-        if ($authorization instanceof Authorization) {
             try {
-                $cancels[] = $authorization->cancel($amount);
+                $cancellation = $authorize->cancel($cancelAmount);
             } catch (HeidelpayApiException $e) {
-                if (ApiResponseCodes::API_ERROR_AUTHORIZE_ALREADY_CANCELLED !== $e->getCode()) {
+                $allowedErrors = [
+                    ApiResponseCodes::API_ERROR_ALREADY_CANCELLED,
+                    ApiResponseCodes::API_ERROR_ALREADY_CHARGED
+                ];
+
+                if (!in_array($e->getCode(), $allowedErrors, true)) {
                     throw $e;
                 }
-                $exceptions[] = $e;
             }
         }
-        return array($cancels, $exceptions);
+
+        return $cancellation;
     }
 
     /**
diff --git a/src/Resources/PaymentTypes/Paypage.php b/src/Resources/PaymentTypes/Paypage.php
index f62b2c82..7feb3234 100644
--- a/src/Resources/PaymentTypes/Paypage.php
+++ b/src/Resources/PaymentTypes/Paypage.php
@@ -101,9 +101,9 @@ class Paypage extends BasePaymentType
      */
     public function __construct(float $amount, string $currency, string $returnUrl)
     {
-        $this->amount = $amount;
-        $this->currency = $currency;
-        $this->returnUrl = $returnUrl;
+        $this->setAmount($amount);
+        $this->setCurrency($currency);
+        $this->setReturnUrl($returnUrl);
     }
 
     //<editor-fold desc="Getters/Setters">
@@ -123,7 +123,7 @@ public function getAmount(): float
      */
     public function setAmount(float $amount): Paypage
     {
-        $this->amount = $amount;
+        $this->amount = round($amount, 4);
         return $this;
     }
 
diff --git a/src/Resources/TransactionTypes/Authorization.php b/src/Resources/TransactionTypes/Authorization.php
index a6c37f31..5afa3259 100755
--- a/src/Resources/TransactionTypes/Authorization.php
+++ b/src/Resources/TransactionTypes/Authorization.php
@@ -83,10 +83,24 @@ public function getAmount()
      */
     public function setAmount($amount): self
     {
-        $this->amount = $amount;
+        $this->amount = $amount !== null ? round($amount, 4) : null;
         return $this;
     }
 
+    /**
+     * @return float|null
+     */
+    public function getCancelledAmount()
+    {
+        $amount = 0.0;
+        foreach ($this->getCancellations() as $cancellation) {
+            /** @var Cancellation $cancellation */
+            $amount += $cancellation->getAmount();
+        }
+
+        return $amount;
+    }
+
     /**
      * @return string|null
      */
diff --git a/src/Resources/TransactionTypes/Cancellation.php b/src/Resources/TransactionTypes/Cancellation.php
index 13a100a6..7ccf6cc3 100755
--- a/src/Resources/TransactionTypes/Cancellation.php
+++ b/src/Resources/TransactionTypes/Cancellation.php
@@ -74,7 +74,7 @@ public function getAmount()
      */
     public function setAmount($amount): Cancellation
     {
-        $this->amount = $amount;
+        $this->amount = $amount !== null ? round($amount, 4) : null;
         return $this;
     }
 
diff --git a/src/Resources/TransactionTypes/Charge.php b/src/Resources/TransactionTypes/Charge.php
index bcad2364..c486938e 100755
--- a/src/Resources/TransactionTypes/Charge.php
+++ b/src/Resources/TransactionTypes/Charge.php
@@ -94,10 +94,32 @@ public function getAmount()
      */
     public function setAmount($amount): self
     {
-        $this->amount = $amount;
+        $this->amount = $amount !== null ? round($amount, 4) : null;
         return $this;
     }
 
+    /**
+     * @return float|null
+     */
+    public function getCancelledAmount()
+    {
+        $amount = 0.0;
+        foreach ($this->getCancellations() as $cancellation) {
+            /** @var Cancellation $cancellation */
+            $amount += $cancellation->getAmount();
+        }
+
+        return $amount;
+    }
+
+    /**
+     * @return float|null
+     */
+    public function getTotalAmount()
+    {
+        return $this->getAmount() - $this->getCancelledAmount();
+    }
+
     /**
      * @return string|null
      */
diff --git a/src/Resources/TransactionTypes/Payout.php b/src/Resources/TransactionTypes/Payout.php
index 396bc087..0e138b4d 100644
--- a/src/Resources/TransactionTypes/Payout.php
+++ b/src/Resources/TransactionTypes/Payout.php
@@ -75,7 +75,7 @@ public function getAmount()
      */
     public function setAmount($amount): self
     {
-        $this->amount = $amount;
+        $this->amount = $amount !== null ? round($amount, 4) : null;
         return $this;
     }
 
diff --git a/src/Resources/TransactionTypes/Shipment.php b/src/Resources/TransactionTypes/Shipment.php
index 389fe585..a9fd4d3f 100755
--- a/src/Resources/TransactionTypes/Shipment.php
+++ b/src/Resources/TransactionTypes/Shipment.php
@@ -30,7 +30,7 @@ class Shipment extends AbstractTransactionType
 {
     use HasInvoiceId;
 
-    /** @var float $amount */
+    /** @var float|null $amount */
     protected $amount;
 
     //<editor-fold desc="Getters/Setters">
@@ -48,9 +48,9 @@ public function getAmount()
      *
      * @return Shipment
      */
-    public function setAmount(float $amount): Shipment
+    public function setAmount($amount): Shipment
     {
-        $this->amount = $amount;
+        $this->amount = $amount !== null ? round($amount, 4) : null;
         return $this;
     }
 
diff --git a/src/Services/EnvironmentService.php b/src/Services/EnvironmentService.php
index e0bb9a0a..4a43f2e7 100755
--- a/src/Services/EnvironmentService.php
+++ b/src/Services/EnvironmentService.php
@@ -33,6 +33,11 @@ class EnvironmentService
 
     const ENV_VAR_NAME_DISABLE_TEST_LOGGING = 'HEIDELPAY_MGW_DISABLE_TEST_LOGGING';
 
+    const ENV_VAR_TEST_PRIVATE_KEY = 'HEIDELPAY_MGW_TEST_PRIVATE_KEY';
+    const ENV_VAR_TEST_PUBLIC_KEY = 'HEIDELPAY_MGW_TEST_PUBLIC_KEY';
+    const DEFAULT_TEST_PRIVATE_KEY = 's-priv-2a102ZMq3gV4I3zJ888J7RR6u75oqK3n';
+    const DEFAULT_TEST_PUBLIC_KEY  = 's-pub-2a10ifVINFAjpQJ9qW8jBe5OJPBx6Gxa';
+
     const ENV_VAR_NAME_TIMEOUT = 'HEIDELPAY_MGW_TIMEOUT';
     const ENV_VAR_DEFAULT_TIMEOUT = 60;
 
@@ -68,4 +73,28 @@ public static function getTimeout(): int
         $timeout = $_SERVER[self::ENV_VAR_NAME_TIMEOUT] ?? '';
         return is_numeric($timeout) ? (int)$timeout : self::ENV_VAR_DEFAULT_TIMEOUT;
     }
+
+    /**
+     * Returns the private key string set via environment variable.
+     * Returns the default key if the environment variable is not set.
+     *
+     * @return string
+     */
+    public function getTestPrivateKey(): string
+    {
+        $key = $_SERVER[self::ENV_VAR_TEST_PRIVATE_KEY] ?? '';
+        return empty($key) ? self::DEFAULT_TEST_PRIVATE_KEY : $key;
+    }
+
+    /**
+     * Returns the public key string set via environment variable.
+     * Returns the default key if the environment variable is not set.
+     *
+     * @return string
+     */
+    public function getTestPublicKey(): string
+    {
+        $key = $_SERVER[self::ENV_VAR_TEST_PUBLIC_KEY] ?? '';
+        return empty($key) ? self::DEFAULT_TEST_PUBLIC_KEY : $key;
+    }
 }
diff --git a/src/Services/ResourceService.php b/src/Services/ResourceService.php
index b3f35c00..ae4c532a 100755
--- a/src/Services/ResourceService.php
+++ b/src/Services/ResourceService.php
@@ -417,14 +417,16 @@ public function fetchPaymentByOrderId($orderId): Payment
     /**
      * Fetch public key and configured payment types from API.
      *
+     * @param bool $detailed If this flag is set detailed information are fetched.
+     *
      * @return Keypair
      *
      * @throws HeidelpayApiException
      * @throws RuntimeException
      */
-    public function fetchKeypair(): AbstractHeidelpayResource
+    public function fetchKeypair($detailed = false): AbstractHeidelpayResource
     {
-        $keyPair = (new Keypair())->setParentResource($this->heidelpay);
+        $keyPair = (new Keypair())->setParentResource($this->heidelpay)->setDetailed($detailed);
         return $this->fetch($keyPair);
     }
 
diff --git a/test/BasePaymentTest.php b/test/BasePaymentTest.php
index cc618612..3edd9fdc 100755
--- a/test/BasePaymentTest.php
+++ b/test/BasePaymentTest.php
@@ -36,6 +36,7 @@
 use heidelpayPHP\Resources\TransactionTypes\AbstractTransactionType;
 use heidelpayPHP\Resources\TransactionTypes\Authorization;
 use heidelpayPHP\Resources\TransactionTypes\Charge;
+use heidelpayPHP\Services\EnvironmentService;
 use heidelpayPHP\test\Fixtures\CustomerFixtureTrait;
 use PHPUnit\Framework\AssertionFailedError;
 use PHPUnit\Framework\Exception;
@@ -52,13 +53,6 @@ class BasePaymentTest extends TestCase
 
     const RETURN_URL = 'http://dev.heidelpay.com';
 
-    // SAQ-D certified merchants are allowed to handle and store CreditCard data,
-    // thus can create a CreditCard via this SDK.
-    // If the merchant is not certified to handle the CreditCard data SAQ-A applies
-    // in which case the merchant has to embed our iFrame via JS (UIComponents).
-    const PRIVATE_KEY = 's-priv-2a102ZMq3gV4I3zJ888J7RR6u75oqK3n';
-    const PUBLIC_KEY  = 's-pub-2a10ifVINFAjpQJ9qW8jBe5OJPBx6Gxa';
-
     /**
      * {@inheritDoc}
      *
@@ -66,8 +60,8 @@ class BasePaymentTest extends TestCase
      */
     protected function setUp()
     {
-        $this->heidelpay = (new Heidelpay(self::PRIVATE_KEY))
-            ->setDebugHandler(new TestDebugHandler())->setDebugMode(true);
+        $privateKey = (new EnvironmentService())->getTestPrivateKey();
+        $this->heidelpay = (new Heidelpay($privateKey))->setDebugHandler(new TestDebugHandler())->setDebugMode(true);
     }
 
     //<editor-fold desc="Custom asserts">
@@ -205,16 +199,18 @@ protected function createCardObject(): Card
     /**
      * Creates and returns an Authorization object with the API which can be used in test methods.
      *
+     * @param float $amount
+     *
      * @return Authorization
      *
-     * @throws RuntimeException
      * @throws HeidelpayApiException
+     * @throws RuntimeException
      */
-    public function createCardAuthorization(): Authorization
+    public function createCardAuthorization($amount = 100.0): Authorization
     {
         $card          = $this->heidelpay->createPaymentType($this->createCardObject());
         $orderId       = microtime(true);
-        $authorization = $this->heidelpay->authorize(100.0, 'EUR', $card, self::RETURN_URL, null, $orderId, null, null, false);
+        $authorization = $this->heidelpay->authorize($amount, 'EUR', $card, self::RETURN_URL, null, $orderId, null, null, false);
         return $authorization;
     }
 
@@ -238,15 +234,17 @@ public function createPaypalAuthorization(): Authorization
     /**
      * Creates and returns a Charge object with the API which can be used in test methods.
      *
+     * @param float $amount
+     *
      * @return Charge
      *
-     * @throws RuntimeException
      * @throws HeidelpayApiException
+     * @throws RuntimeException
      */
-    public function createCharge(): Charge
+    public function createCharge($amount = 100.0): Charge
     {
         $card = $this->heidelpay->createPaymentType(new SepaDirectDebit('DE89370400440532013000'));
-        return $this->heidelpay->charge(100.0, 'EUR', $card, self::RETURN_URL);
+        return $this->heidelpay->charge($amount, 'EUR', $card, self::RETURN_URL);
     }
 
     /**
diff --git a/test/integration/BasketTest.php b/test/integration/BasketTest.php
index f544c105..d87a5253 100755
--- a/test/integration/BasketTest.php
+++ b/test/integration/BasketTest.php
@@ -185,7 +185,7 @@ public function authorizeTransactionsShouldPassAlongTheBasketIdIfSet()
 
         /** @var Paypal $paypal */
         $paypal = $this->heidelpay->createPaymentType(new Paypal());
-        $authorize = $paypal->authorize(10.0, 'EUR', 'https://heidelpay.com', null, null, null, $basket);
+        $authorize = $paypal->authorize(123.4, 'EUR', 'https://heidelpay.com', null, null, null, $basket);
 
         $fetchedPayment = $this->heidelpay->fetchPayment($authorize->getPaymentId());
         $this->assertEquals($basket->expose(), $fetchedPayment->getBasket()->expose());
@@ -208,7 +208,7 @@ public function chargeTransactionsShouldPassAlongTheBasketIdIfSet()
         $this->heidelpay->createPaymentType($sdd);
 
         $customer = $this->getMaximumCustomerInclShippingAddress()->setShippingAddress($this->getBillingAddress());
-        $charge   = $sdd->charge(100.0, 'EUR', self::RETURN_URL, $customer, null, null, $basket);
+        $charge   = $sdd->charge(123.4, 'EUR', self::RETURN_URL, $customer, null, null, $basket);
 
         $fetchedPayment = $this->heidelpay->fetchPayment($charge->getPaymentId());
         $this->assertEquals($basket->expose(), $fetchedPayment->getBasket()->expose());
@@ -233,7 +233,7 @@ public function authorizeTransactionsShouldCreateBasketIfItDoesNotExistYet()
 
         /** @var Paypal $paypal */
         $paypal = $this->heidelpay->createPaymentType(new Paypal());
-        $authorize = $paypal->authorize(10.0, 'EUR', 'https://heidelpay.com', null, null, null, $basket);
+        $authorize = $paypal->authorize(123.4, 'EUR', 'https://heidelpay.com', null, null, null, $basket);
         $this->assertNotEmpty($basket->getId());
 
         $fetchedPayment = $this->heidelpay->fetchPayment($authorize->getPaymentId());
@@ -260,7 +260,7 @@ public function chargeTransactionsShouldCreateBasketIfItDoesNotExistYet()
 
         /** @var Paypal $paypal */
         $paypal = $this->heidelpay->createPaymentType(new Paypal());
-        $charge = $paypal->charge(10.0, 'EUR', 'https://heidelpay.com', null, null, null, $basket);
+        $charge = $paypal->charge(123.4, 'EUR', 'https://heidelpay.com', null, null, null, $basket);
         $this->assertNotEmpty($basket->getId());
 
         $fetchedPayment = $this->heidelpay->fetchPayment($charge->getPaymentId());
diff --git a/test/integration/CustomerTest.php b/test/integration/CustomerTest.php
index 590fb2eb..c42da02f 100755
--- a/test/integration/CustomerTest.php
+++ b/test/integration/CustomerTest.php
@@ -32,8 +32,8 @@
 use heidelpayPHP\Resources\Payment;
 use heidelpayPHP\Resources\PaymentTypes\Paypal;
 use heidelpayPHP\test\BasePaymentTest;
-use function microtime;
 use RuntimeException;
+use function microtime;
 
 class CustomerTest extends BasePaymentTest
 {
@@ -162,7 +162,8 @@ public function customerCanBeFetchedByObjectWithData(Customer $customer)
      */
     public function transactionShouldCreateAndReferenceCustomerIfItDoesNotExistYet()
     {
-        $customer = $this->getMaximumCustomerInclShippingAddress();
+        $customerId = 'customer' . $this->generateRandomId();
+        $customer   = $this->getMaximumCustomerInclShippingAddress()->setCustomerId($customerId);
 
         /** @var Paypal $paypal */
         $paypal = $this->heidelpay->createPaymentType(new Paypal());
diff --git a/test/integration/KeyTest.php b/test/integration/KeypairTest.php
old mode 100755
new mode 100644
similarity index 73%
rename from test/integration/KeyTest.php
rename to test/integration/KeypairTest.php
index a8b99fa7..f1cdcc8f
--- a/test/integration/KeyTest.php
+++ b/test/integration/KeypairTest.php
@@ -29,7 +29,7 @@
 use heidelpayPHP\test\BasePaymentTest;
 use RuntimeException;
 
-class KeyTest extends BasePaymentTest
+class KeypairTest extends BasePaymentTest
 {
     /**
      * Validate valid keys are accepted.
@@ -64,7 +64,7 @@ public function invalidKeysShouldResultInException($key)
     }
 
     /**
-     * Verify key pair command can be performed.
+     * Verify key pair config can be fetched.
      *
      * @test
      *
@@ -76,6 +76,26 @@ public function keypairShouldReturnExpectedValues()
         $keypair = $this->heidelpay->fetchKeypair();
         $this->assertNotNull($keypair);
         $this->assertNotEmpty($keypair->getPublicKey());
+        $this->assertNotEmpty($keypair->getPrivateKey());
         $this->assertNotEmpty($keypair->getAvailablePaymentTypes());
+        $this->assertNotEmpty($keypair->getSecureLevel());
+    }
+
+    /**
+     * Verify key pair config can be fetched with details.
+     *
+     * @test
+     *
+     * @throws RuntimeException
+     * @throws HeidelpayApiException
+     */
+    public function keypairShouldBeFetchableWithDetails()
+    {
+        $keypair = $this->heidelpay->fetchKeypair(true);
+        $this->assertNotNull($keypair);
+        $this->assertNotEmpty($keypair->getPublicKey());
+        $this->assertNotEmpty($keypair->getPrivateKey());
+        $this->assertNotEmpty($keypair->getPaymentTypes());
+        $this->assertNotEmpty($keypair->getSecureLevel());
     }
 }
diff --git a/test/integration/PaymentCancelTest.php b/test/integration/PaymentCancelTest.php
new file mode 100644
index 00000000..8b922c6d
--- /dev/null
+++ b/test/integration/PaymentCancelTest.php
@@ -0,0 +1,525 @@
+<?php
+/**
+ * This class defines integration tests to verify functionality of the Payment charge method.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/test/integration
+ */
+namespace heidelpayPHP\test\integration;
+
+use heidelpayPHP\Exceptions\HeidelpayApiException;
+use heidelpayPHP\Resources\PaymentTypes\Invoice;
+use heidelpayPHP\test\BasePaymentTest;
+use PHPUnit\Framework\AssertionFailedError;
+use PHPUnit\Framework\Exception;
+use RuntimeException;
+
+class PaymentCancelTest extends BasePaymentTest
+{
+    //<editor-fold desc="Tests">
+
+    /**
+     * Verify full cancel on cancelled authorize returns empty array.
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function doubleCancelOnAuthorizeShouldReturnEmptyArray()
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $cancellations = $payment->cancelAmount();
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 0.0, 0.0);
+        $this->assertCount(1, $cancellations);
+
+        $newCancellations = $payment->cancelAmount();
+        $this->assertCount(0, $newCancellations);
+    }
+
+    /**
+     * Verify full cancel on charge.
+     * AND
+     * Return empty array if charge is already fully cancelled.
+     * PHPLIB-228 - Case 1 + double cancel
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function cancelOnChargeAndDoubleCancel()
+    {
+        $charge = $this->createCharge(123.44);
+        $payment = $this->heidelpay->fetchPayment($charge->getPaymentId());
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 123.44, 123.44, 0.0);
+
+        $cancellations = $payment->cancelAmount();
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 123.44, 123.44);
+        $this->assertCount(1, $cancellations);
+
+        $payment = $this->heidelpay->fetchPayment($charge->getPaymentId());
+        $newCancellations = $payment->cancelAmount();
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 123.44, 123.44);
+        $this->assertCount(0, $newCancellations);
+    }
+
+    /**
+     * Verify full cancel on multiple charges.
+     * PHPLIB-228 - Case 2
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function fullCancelOnPaymentWithAuthorizeAndMultipleChargesShouldBePossible()
+    {
+        $authorization = $this->createCardAuthorization(123.44);
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 123.44, 0.0, 123.44, 0.0);
+
+        $payment->charge(100.44);
+        $this->assertTrue($payment->isPartlyPaid());
+        $this->assertAmounts($payment, 23.0, 100.44, 123.44, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $payment->charge(23.00);
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 123.44, 123.44, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(2, $payment->cancelAmount());
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 123.44, 123.44);
+    }
+
+    /**
+     * Verify partial cancel on charge.
+     * PHPLIB-228 - Case 3
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function partialCancelAndFullCancelOnSingleCharge()
+    {
+        $charge = $this->createCharge(222.33);
+        $payment = $this->heidelpay->fetchPayment($charge->getPaymentId());
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 222.33, 222.33, 0.0);
+
+        $this->assertCount(1, $payment->cancelAmount(123.12));
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 99.21, 222.33, 123.12);
+
+        $payment = $this->heidelpay->fetchPayment($charge->getPaymentId());
+        $this->assertCount(1, $payment->cancelAmount(99.21));
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 222.33, 222.33);
+    }
+
+    /**
+     * Verify partial cancel on multiple charges (cancel < last charge).
+     * PHPLIB-228 - Case 4 + 5
+     *
+     * @test
+     * @dataProvider partCancelDataProvider
+     *
+     * @param float $amount
+     * @param int   $numberCancels
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function partialCancelOnMultipleChargedAuthorization($amount, $numberCancels)
+    {
+        $authorizeAmount = 123.44;
+        $authorization = $this->createCardAuthorization($authorizeAmount);
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+
+        $payment->charge(23.00);
+        $this->assertTrue($payment->isPartlyPaid());
+        $this->assertAmounts($payment, 100.44, 23.0, $authorizeAmount, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $payment->charge(100.44);
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, $authorizeAmount, $authorizeAmount, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount($numberCancels, $payment->cancelAmount($amount));
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, $authorizeAmount - $amount, $authorizeAmount, $amount);
+    }
+
+    /**
+     * Verify full cancel on authorize.
+     * PHPLIB-228 - Case 6
+     *
+     * @test
+     * @dataProvider fullCancelDataProvider
+     *
+     * @param float $amount
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function fullCancelOnAuthorize($amount)
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $this->assertCount(1, $payment->cancelAmount($amount));
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 0.0, 0.0);
+    }
+
+    /**
+     * Verify partial cancel on authorize.
+     * PHPLIB-228 - Case 7
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function fullCancelOnPartCanceledAuthorize()
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $this->assertCount(1, $payment->cancelAmount(10.0));
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 90.0, 0.0, 90.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(1, $payment->cancelAmount(10.0));
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 80.0, 0.0, 80.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(1, $payment->cancelAmount());
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 0.0, 0.0);
+    }
+
+    /**
+     * Verify full cancel on fully charged authorize.
+     * PHPLIB-228 - Case 8
+     *
+     * @test
+     * @dataProvider fullCancelDataProvider
+     *
+     * @param float $amount The amount to be cancelled.
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function fullCancelOnFullyChargedAuthorize($amount)
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $payment->charge();
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 100.0, 100.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(1, $payment->cancelAmount($amount));
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 100.0, 100.0);
+    }
+
+    /**
+     * Verify full cancel on partly charged authorize.
+     * PHPLIB-228 - Case 9
+     *
+     * @test
+     * @dataProvider fullCancelDataProvider
+     *
+     * @param $amount
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function fullCancelOnPartlyChargedAuthorizeShouldBePossible($amount)
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $payment->charge(50.0);
+        $this->assertTrue($payment->isPartlyPaid());
+        $this->assertAmounts($payment, 50.0, 50.0, 100.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(2, $payment->cancelAmount($amount));
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 50.0, 50.0);
+    }
+
+    /**
+     * Verify part cancel on uncharged authorize.
+     * PHPLIB-228 - Case 10
+     *
+     * @test
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function partCancelOnUnchargedAuthorize()
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $this->assertCount(1, $payment->cancelAmount(50.0));
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 50.0, 0.0, 50.0, 0.0);
+    }
+
+    /**
+     * Verify part cancel on partly charged authorize with cancel amount lt charged amount.
+     * PHPLIB-228 - Case 11
+     *
+     * @test
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function partCancelOnPartlyChargedAuthorizeWithAmountLtCharged()
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $payment->charge(25.0);
+        $this->assertTrue($payment->isPartlyPaid());
+        $this->assertAmounts($payment, 75.0, 25.0, 100.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(1, $payment->cancelAmount(20.0));
+        $this->assertTrue($payment->isPartlyPaid());
+        $this->assertAmounts($payment, 55.0, 25.0, 80.0, 0.0);
+    }
+
+    /**
+     * Verify part cancel on partly charged authorize with cancel amount gt charged amount.
+     * PHPLIB-228 - Case 12
+     *
+     * @test
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function partCancelOnPartlyChargedAuthorizeWithAmountGtCharged()
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $payment->charge(40.0);
+        $this->assertTrue($payment->isPartlyPaid());
+        $this->assertAmounts($payment, 60.0, 40.0, 100.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(2, $payment->cancelAmount(80.0));
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 20.0, 40.0, 20.0);
+    }
+
+    /**
+     * Verify full cancel on initial iv charge (reversal)
+     * PHPLIB-228 - Case 13
+     *
+     * @test
+     * @dataProvider fullCancelDataProvider
+     *
+     * @param float $amount
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function fullCancelOnInitialInvoiceCharge($amount)
+    {
+        /** @var Invoice $invoice */
+        $invoice = $this->heidelpay->createPaymentType(new Invoice());
+        $charge = $invoice->charge(100.0, 'EUR', self::RETURN_URL);
+        $payment = $this->heidelpay->fetchPayment($charge->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $this->assertCount(1, $payment->cancelAmount($amount));
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 0.0, 0.0);
+    }
+
+    /**
+     * Verify part cancel on initial iv charge (reversal)
+     * PHPLIB-228 - Case 14
+     *
+     * @test
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function partCancelOnInitialInvoiceChargeShouldBePossible()
+    {
+        /** @var Invoice $invoice */
+        $invoice = $this->heidelpay->createPaymentType(new Invoice());
+        $charge = $invoice->charge(100.0, 'EUR', self::RETURN_URL);
+        $payment = $this->heidelpay->fetchPayment($charge->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $this->assertCount(1, $payment->cancelAmount(50.0));
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 50.0, 0.0, 50.0, 0.0);
+    }
+
+    /**
+     * Verify cancelling more than was charged.
+     * PHPLIB-228 - Case 15
+     *
+     * @test
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function cancelMoreThanWasCharged()
+    {
+        $charge = $this->createCharge(50.0);
+        $payment = $this->heidelpay->fetchPayment($charge->getPaymentId());
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 50.0, 50.0, 0.0);
+
+        $this->assertCount(1, $payment->cancelAmount(100.0));
+        $this->assertTrue($payment->isCanceled());
+        $this->assertAmounts($payment, 0.0, 0.0, 50.0, 50.0);
+    }
+
+    /**
+     * Verify second cancel on partly cancelled charge.
+     * PHPLIB-228 - Case 16
+     *
+     * @test
+     *
+     * @throws AssertionFailedError
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     */
+    public function secondCancelExceedsRemainderOfPartlyCancelledCharge()
+    {
+        $authorization = $this->createCardAuthorization();
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertTrue($payment->isPending());
+        $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0);
+
+        $payment->charge(50.0);
+        $this->assertTrue($payment->isPartlyPaid());
+        $this->assertAmounts($payment, 50.0, 50.0, 100.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $payment->charge(50.0);
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 100.0, 100.0, 0.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(1, $payment->cancelAmount(40.0));
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 60.0, 100.0, 40.0);
+
+        $payment = $this->heidelpay->fetchPayment($authorization->getPaymentId());
+        $this->assertCount(2, $payment->cancelAmount(30.0));
+        $this->assertTrue($payment->isCompleted());
+        $this->assertAmounts($payment, 0.0, 30.0, 100.0, 70.0);
+    }
+
+    //</editor-fold>
+
+    //<editor-fold desc="Data Providers">
+
+    /**
+     * @return array
+     */
+    public function partCancelDataProvider(): array
+    {
+        return [
+            'cancel amount lt last charge' => [20, 1],
+            'cancel amount eq to last charge' => [23, 1],
+            'cancel amount gt last charge' => [40, 2]
+        ];
+    }
+
+    /**
+     * @return array
+     */
+    public function fullCancelDataProvider(): array
+    {
+        return [
+            'no amount given' => [null],
+            'amount given' => [100.0]
+        ];
+    }
+
+    //</editor-fold>
+}
diff --git a/test/integration/PaymentTest.php b/test/integration/PaymentTest.php
index 6bb61939..41261c3a 100755
--- a/test/integration/PaymentTest.php
+++ b/test/integration/PaymentTest.php
@@ -129,82 +129,6 @@ public function partialChargeAfterAuthorization()
         $this->assertEquals('10.0', $charge->getAmount());
     }
 
-    /**
-     * Verify full cancel on authorize throws exception if already canceled.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws RuntimeException
-     */
-    public function fullCancelOnAuthorizeShouldThrowExceptionIfAlreadyCanceled()
-    {
-        $authorization = $this->createCardAuthorization();
-        $fetchedPayment = $this->heidelpay->fetchPayment($authorization->getPayment()->getId());
-        $cancel = $fetchedPayment->getAuthorization()->cancel();
-        $this->assertNotNull($cancel);
-        $this->assertEquals('s-cnl-1', $cancel->getId());
-        $this->assertEquals($authorization->getAmount(), $cancel->getAmount());
-
-        $this->expectException(HeidelpayApiException::class);
-        $this->expectExceptionCode(ApiResponseCodes::API_ERROR_AUTHORIZE_ALREADY_CANCELLED);
-        $fetchedPayment->cancel();
-    }
-
-    /**
-     * Verify partial cancel on authorize.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws RuntimeException
-     */
-    public function partialCancelOnAuthorizeShouldBePossible()
-    {
-        $authorization = $this->createCardAuthorization();
-        $fetchedPayment = $this->heidelpay->fetchPayment($authorization->getPayment()->getId());
-        $this->assertAmounts($fetchedPayment, 100.0, 0, 100.0, 0);
-
-        $cancel = $fetchedPayment->cancel(10.0);
-        $this->assertNotNull($cancel);
-        $this->assertEquals('s-cnl-1', $cancel->getId());
-        $this->assertEquals('10.0', $cancel->getAmount());
-        $this->assertAmounts($fetchedPayment, 90.0, 0, 90.0, 0);
-    }
-
-    /**
-     * Verify full cancel on charge.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws RuntimeException
-     */
-    public function fullCancelOnChargeShouldBePossible()
-    {
-        $charge = $this->createCharge();
-        $fetchedPayment = $this->heidelpay->fetchPayment($charge->getPayment()->getId());
-        $fetchedCharge = $fetchedPayment->getCharge('s-chg-1');
-        $cancellation = $fetchedCharge->cancel();
-        $this->assertNotNull($cancellation);
-    }
-
-    /**
-     * Verify partial cancel on charge.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws RuntimeException
-     */
-    public function partialCancelShouldBePossible()
-    {
-        $charge = $this->createCharge();
-        $fetchedPayment = $this->heidelpay->fetchPayment($charge->getPayment()->getId());
-        $cancel = $fetchedPayment->getChargeByIndex(0)->cancel(10.0);
-        $this->assertNotNull($cancel);
-    }
-
     /**
      * Verify authorization on payment.
      *
diff --git a/test/integration/PaymentTypes/InvoiceFactoringTest.php b/test/integration/PaymentTypes/InvoiceFactoringTest.php
index efe8e223..e4483d45 100755
--- a/test/integration/PaymentTypes/InvoiceFactoringTest.php
+++ b/test/integration/PaymentTypes/InvoiceFactoringTest.php
@@ -139,15 +139,7 @@ public function invoiceFactoringShouldBeChargeable(InvoiceFactoring $invoiceFact
         $customer->setShippingAddress($customer->getBillingAddress());
 
         $basket = $this->createBasket();
-        $charge = $invoiceFactoring->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $basket->getOrderId(),
-            null,
-            $basket
-        );
+        $charge = $invoiceFactoring->charge(123.4, 'EUR', self::RETURN_URL, $customer, $basket->getOrderId(), null, $basket);
         $this->assertNotNull($charge);
         $this->assertNotEmpty($charge->getId());
         $this->assertNotEmpty($charge->getIban());
@@ -178,15 +170,7 @@ public function verifyInvoiceFactoringIsNotShippableWoInvoiceIdOnHeidelpayObject
         $customer->setShippingAddress($customer->getBillingAddress());
 
         $basket = $this->createBasket();
-        $charge = $invoiceFactoring->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $basket->getOrderId(),
-            null,
-            $basket
-        );
+        $charge = $invoiceFactoring->charge(123.4, 'EUR', self::RETURN_URL, $customer, $basket->getOrderId(), null, $basket);
 
         // perform shipment
         $payment = $charge->getPayment();
@@ -215,15 +199,7 @@ public function verifyInvoiceFactoringIsNotShippableWoInvoiceIdOnPaymentObject()
         $customer->setShippingAddress($customer->getBillingAddress());
 
         $basket = $this->createBasket();
-        $charge = $invoiceFactoring->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $basket->getOrderId(),
-            null,
-            $basket
-        );
+        $charge = $invoiceFactoring->charge(123.4, 'EUR', self::RETURN_URL, $customer, $basket->getOrderId(), null, $basket);
 
         // perform shipment
         $payment = $charge->getPayment();
@@ -251,15 +227,7 @@ public function verifyInvoiceFactoringShipmentWithInvoiceIdOnHeidelpayObject()
         $customer->setShippingAddress($customer->getBillingAddress());
 
         $basket = $this->createBasket();
-        $charge = $invoiceFactoring->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $basket->getOrderId(),
-            null,
-            $basket
-        );
+        $charge = $invoiceFactoring->charge(123.4, 'EUR', self::RETURN_URL, $customer, $basket->getOrderId(), null, $basket);
 
         // perform shipment
         $payment   = $charge->getPayment();
@@ -288,15 +256,7 @@ public function verifyInvoiceFactoringShipmentWithInvoiceIdOnPaymentObject()
         $customer->setShippingAddress($customer->getBillingAddress());
 
         $basket = $this->createBasket();
-        $charge = $invoiceFactoring->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $basket->getOrderId(),
-            null,
-            $basket
-        );
+        $charge = $invoiceFactoring->charge(123.4, 'EUR', self::RETURN_URL, $customer, $basket->getOrderId(), null, $basket);
 
         $payment   = $charge->getPayment();
         $invoiceId = substr(str_replace(['0.',' '], '', microtime(false)), 0, 16);
@@ -323,17 +283,7 @@ public function verifyInvoiceFactoringShipmentWithPreSetInvoiceId()
 
         $basket = $this->createBasket();
         $invoiceId = substr(str_replace(['0.',' '], '', microtime(false)), 0, 16);
-        $charge = $invoiceFactoring->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $basket->getOrderId(),
-            null,
-            $basket,
-            null,
-            $invoiceId
-        );
+        $charge = $invoiceFactoring->charge(123.4, 'EUR', self::RETURN_URL, $customer, $basket->getOrderId(), null, $basket, null, $invoiceId);
 
         $payment   = $charge->getPayment();
         $shipment  = $this->heidelpay->ship($payment);
diff --git a/test/integration/PaymentTypes/PaypageTest.php b/test/integration/PaymentTypes/PaypageTest.php
index decd9668..024988b1 100644
--- a/test/integration/PaymentTypes/PaypageTest.php
+++ b/test/integration/PaymentTypes/PaypageTest.php
@@ -66,7 +66,7 @@ public function maximumPaypageChargeShouldBeCreatable()
         $basket = $this->createBasket();
         $customer = CustomerFactory::createCustomer('Max', 'Mustermann');
         $invoiceId = $this->generateRandomId();
-        $paypage = (new Paypage(100.0, 'EUR', self::RETURN_URL))
+        $paypage = (new Paypage(123.4, 'EUR', self::RETURN_URL))
             ->setLogoImage('https://dev.heidelpay.com/devHeidelpay_400_180.jpg')
             ->setFullPageImage('https://www.heidelpay.com/fileadmin/content/header-Imges-neu/Header_Phone_12.jpg')
             ->setShopName('My Test Shop')
@@ -120,7 +120,7 @@ public function maximumPaypageAuthorizeShouldBeCreatable()
         $basket = $this->createBasket();
         $customer = CustomerFactory::createCustomer('Max', 'Mustermann');
         $invoiceId = $this->generateRandomId();
-        $paypage = (new Paypage(100.0, 'EUR', self::RETURN_URL))
+        $paypage = (new Paypage(123.4, 'EUR', self::RETURN_URL))
             ->setLogoImage('https://dev.heidelpay.com/devHeidelpay_400_180.jpg')
             ->setFullPageImage('https://www.heidelpay.com/fileadmin/content/header-Imges-neu/Header_Phone_12.jpg')
             ->setShopName('My Test Shop')
diff --git a/test/integration/TransactionTypes/AuthorizationTest.php b/test/integration/TransactionTypes/AuthorizationTest.php
index f2bde4e5..9da2d582 100644
--- a/test/integration/TransactionTypes/AuthorizationTest.php
+++ b/test/integration/TransactionTypes/AuthorizationTest.php
@@ -181,11 +181,11 @@ public function authorizeShouldAcceptAllParameters()
         $invoiceId = $this->generateRandomId();
         $paymentReference = 'paymentReference';
 
-        $authorize = $card->authorize(100.0, 'EUR', self::RETURN_URL, $customer, $orderId, $metadata, $basket, true, $invoiceId, $paymentReference);
+        $authorize = $card->authorize(123.4, 'EUR', self::RETURN_URL, $customer, $orderId, $metadata, $basket, true, $invoiceId, $paymentReference);
         $payment = $authorize->getPayment();
 
         $this->assertSame($card, $payment->getPaymentType());
-        $this->assertEquals(100.0, $authorize->getAmount());
+        $this->assertEquals(123.4, $authorize->getAmount());
         $this->assertEquals('EUR', $authorize->getCurrency());
         $this->assertEquals(self::RETURN_URL, $authorize->getReturnUrl());
         $this->assertSame($customer, $payment->getCustomer());
diff --git a/test/integration/TransactionTypes/ChargeTest.php b/test/integration/TransactionTypes/ChargeTest.php
index 12734555..4a3baf19 100644
--- a/test/integration/TransactionTypes/ChargeTest.php
+++ b/test/integration/TransactionTypes/ChargeTest.php
@@ -105,23 +105,12 @@ public function chargeShouldAcceptAllParameters()
         $paymentReference = 'paymentReference';
 
         // perform request
-        $charge = $paymentType->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $orderId,
-            $metadata,
-            $basket,
-            true,
-            $invoiceId,
-            $paymentReference
-        );
+        $charge = $paymentType->charge(123.4, 'EUR', self::RETURN_URL, $customer, $orderId, $metadata, $basket, true, $invoiceId, $paymentReference);
 
         // verify the data sent and received match
         $payment = $charge->getPayment();
         $this->assertSame($paymentType, $payment->getPaymentType());
-        $this->assertEquals(100.0, $charge->getAmount());
+        $this->assertEquals(123.4, $charge->getAmount());
         $this->assertEquals('EUR', $charge->getCurrency());
         $this->assertEquals(self::RETURN_URL, $charge->getReturnUrl());
         $this->assertSame($customer, $payment->getCustomer());
@@ -166,23 +155,12 @@ public function chargeWithCustomerShouldAcceptAllParameters()
         $paymentReference = 'paymentReference';
 
         // perform request
-        $charge = $ivg->charge(
-            100.0,
-            'EUR',
-            self::RETURN_URL,
-            $customer,
-            $orderId,
-            $metadata,
-            $basket,
-            null,
-            $invoiceId,
-            $paymentReference
-        );
+        $charge = $ivg->charge(123.4, 'EUR', self::RETURN_URL, $customer, $orderId, $metadata, $basket, null, $invoiceId, $paymentReference);
 
         // verify the data sent and received match
         $payment = $charge->getPayment();
         $this->assertSame($ivg, $payment->getPaymentType());
-        $this->assertEquals(100.0, $charge->getAmount());
+        $this->assertEquals(123.4, $charge->getAmount());
         $this->assertEquals('EUR', $charge->getCurrency());
         $this->assertEquals(self::RETURN_URL, $charge->getReturnUrl());
         $this->assertSame($customer, $payment->getCustomer());
diff --git a/test/integration/TransactionTypes/PayoutTest.php b/test/integration/TransactionTypes/PayoutTest.php
index a2554ebd..bf22d408 100644
--- a/test/integration/TransactionTypes/PayoutTest.php
+++ b/test/integration/TransactionTypes/PayoutTest.php
@@ -166,11 +166,11 @@ public function payoutShouldAcceptAllParameters()
         $invoiceId = $this->generateRandomId();
         $paymentReference = 'paymentReference';
 
-        $payout = $card->payout(100.0, 'EUR', self::RETURN_URL, $customer, $orderId, $metadata, $basket, $invoiceId, $paymentReference);
+        $payout = $card->payout(123.4, 'EUR', self::RETURN_URL, $customer, $orderId, $metadata, $basket, $invoiceId, $paymentReference);
         $payment = $payout->getPayment();
 
         $this->assertSame($card, $payment->getPaymentType());
-        $this->assertEquals(100.0, $payout->getAmount());
+        $this->assertEquals(123.4, $payout->getAmount());
         $this->assertEquals('EUR', $payout->getCurrency());
         $this->assertEquals(self::RETURN_URL, $payout->getReturnUrl());
         $this->assertSame($customer, $payment->getCustomer());
diff --git a/test/unit/Exceptions/HeidelpayApiExceptionTest.php b/test/unit/Exceptions/HeidelpayApiExceptionTest.php
index 40b31a31..1c1279ff 100755
--- a/test/unit/Exceptions/HeidelpayApiExceptionTest.php
+++ b/test/unit/Exceptions/HeidelpayApiExceptionTest.php
@@ -42,6 +42,7 @@ public function heidelpayApiExceptionShouldReturnDefaultDataWhenNoneIsSet()
         $exception = new HeidelpayApiException();
         $this->assertEquals(HeidelpayApiException::CLIENT_MESSAGE, $exception->getClientMessage());
         $this->assertEquals(HeidelpayApiException::MESSAGE, $exception->getMerchantMessage());
+        $this->assertEquals('No error id provided', $exception->getErrorId());
         $this->assertEquals('No error code provided', $exception->getCode());
     }
 
@@ -58,9 +59,10 @@ public function heidelpayApiExceptionShouldReturnDefaultDataWhenNoneIsSet()
      */
     public function heidelpayApiExceptionShouldReturnTheGivenData(array $testData, array $expected)
     {
-        $exception = new HeidelpayApiException($testData['message'], $testData['client_message'], $testData['code']);
+        $exception = new HeidelpayApiException($testData['message'], $testData['clientMessage'], $testData['code'], $testData['errorId']);
         $this->assertEquals($expected['message'], $exception->getMerchantMessage());
-        $this->assertEquals($expected['client_message'], $exception->getClientMessage());
+        $this->assertEquals($expected['clientMessage'], $exception->getClientMessage());
+        $this->assertEquals($expected['errorId'], $exception->getErrorId());
         $this->assertEquals($expected['code'], $exception->getCode());
     }
 
@@ -73,28 +75,24 @@ public function exceptionDataProvider(): array
     {
         return [
             'default' => [
-                    'testData' => ['message' => null, 'client_message' => null, 'code' => null],
-                    'expected' => [
-                        'message' => HeidelpayApiException::MESSAGE,
-                        'client_message' => HeidelpayApiException::CLIENT_MESSAGE,
-                        'code' => ''
-                    ]
+                    'testData' => ['message' => null, 'clientMessage' => null, 'code' => null, 'errorId' => null],
+                    'expected' => ['message' => HeidelpayApiException::MESSAGE, 'clientMessage' => HeidelpayApiException::CLIENT_MESSAGE, 'code' => 'No error code provided', 'errorId' => 'No error id provided']
                 ],
             'message' => [
-                    'testData' => ['message' => 'myMessage', 'client_message' => null, 'code' => null],
-                    'expected' => [
-                        'message' => 'myMessage',
-                        'client_message' => HeidelpayApiException::CLIENT_MESSAGE,
-                        'code' => ''
-                    ]
+                    'testData' => ['message' => 'myMessage', 'clientMessage' => null, 'code' => null, 'errorId' => ''],
+                    'expected' => ['message' => 'myMessage', 'clientMessage' => HeidelpayApiException::CLIENT_MESSAGE, 'code' => 'No error code provided', 'errorId' => 'No error id provided']
                 ],
             'clientMessage' => [
-                    'testData' => ['message' => 'myMessage', 'client_message' => 'myClientMessage', 'code' => null],
-                    'expected' => ['message' => 'myMessage', 'client_message' => 'myClientMessage', 'code' => null]
+                    'testData' => ['message' => 'myMessage', 'clientMessage' => 'myClientMessage', 'code' => null, 'errorId' => null],
+                    'expected' => ['message' => 'myMessage', 'clientMessage' => 'myClientMessage', 'code' => 'No error code provided', 'errorId' => 'No error id provided']
                 ],
             'code' => [
-                    'testData' => ['message' => 'myMessage', 'client_message' => 'myClientMessage', 'code' => 'myCode'],
-                    'expected' => ['message' => 'myMessage', 'client_message' => 'myClientMessage', 'code' => 'myCode']
+                    'testData' => ['message' => 'myMessage', 'clientMessage' => 'myClientMessage', 'code' => 'myCode', 'errorId' => null],
+                    'expected' => ['message' => 'myMessage', 'clientMessage' => 'myClientMessage', 'code' => 'myCode', 'errorId' => 'No error id provided']
+                ],
+            'errorId' => [
+                    'testData' => ['message' => 'myMessage', 'clientMessage' => 'myClientMessage', 'code' => 'myCode', 'errorId' => 'myErrorId'],
+                    'expected' => ['message' => 'myMessage', 'clientMessage' => 'myClientMessage', 'code' => 'myCode', 'errorId' => 'myErrorId']
                 ]
         ];
     }
diff --git a/test/unit/Resources/BasketTest.php b/test/unit/Resources/BasketTest.php
index ea16dc8d..97e31158 100755
--- a/test/unit/Resources/BasketTest.php
+++ b/test/unit/Resources/BasketTest.php
@@ -168,4 +168,21 @@ public function referenceIdShouldBeAutomaticallySetToTheArrayIndexIfItIsNotSet()
         $this->assertEquals('0', $basketItem3->getBasketItemReferenceId());
         $this->assertEquals('1', $basketItem4->getBasketItemReferenceId());
     }
+
+    /**
+     * Verify amount total is replaced by amount total gross.
+     *
+     * @test
+     *
+     * @throws Exception
+     */
+    public function amountTotalSetterGetterAccessAmountTotalGross()
+    {
+        $basket = new Basket();
+        $this->assertEquals($basket->getAmountTotalGross(), $basket->getAmountTotal());
+        $basket->setAmountTotalGross(123.45);
+        $this->assertEquals($basket->getAmountTotalGross(), $basket->getAmountTotal());
+        $basket->setAmountTotal(45.321);
+        $this->assertEquals($basket->getAmountTotalGross(), $basket->getAmountTotal());
+    }
 }
diff --git a/test/unit/Resources/CustomerTest.php b/test/unit/Resources/CustomerTest.php
index b2064a34..c4cc0f3d 100755
--- a/test/unit/Resources/CustomerTest.php
+++ b/test/unit/Resources/CustomerTest.php
@@ -191,6 +191,27 @@ public function gettersAndSettersOfCompanyInfoShouldWork()
         $this->assertSame($companyInfo, $customer->getCompanyInfo());
     }
 
+    /**
+     * Verify removeRestrictedSymbols method works.
+     *
+     * @test
+     *
+     * @dataProvider removeRestrictedSymbolsMethodShouldReturnTheCorrectValueDP
+     *
+     * @throws Exception
+     *
+     * @param mixed $value
+     * @param mixed $expected
+     */
+    public function removeRestrictedSymbolsMethodShouldReturnTheCorrectValue($value, $expected)
+    {
+        $companyInfo = new CompanyInfo();
+        $this->assertNull($companyInfo->getFunction());
+
+        $companyInfo->setFunction($value);
+        $this->assertEquals($expected, $companyInfo->getFunction());
+    }
+
     /**
      * Verify salutation only uses the given values.
      *
@@ -243,11 +264,31 @@ public function fetchCustomerByOrderIdShouldCreateCustomerObjectWithCustomerIdAn
                 static function ($customer) use ($heidelpay) {
                     return $customer instanceof Customer &&
                         $customer->getCustomerId() === 'myCustomerId' &&
-                        $customer->getId() === 'myCustomerId' &&
                         $customer->getHeidelpayObject() === $heidelpay;
                 }));
 
         /** @var ResourceService $resourceSrvMock */
         $resourceSrvMock->fetchCustomerByExtCustomerId('myCustomerId');
     }
+
+    //<editor-fold desc="Data providers">
+
+    /**
+     * DataProvider for removeRestrictedSymbolsMethodShouldReturnTheCorrectValue.
+     */
+    public function removeRestrictedSymbolsMethodShouldReturnTheCorrectValueDP(): array
+    {
+        return [
+            'null' => [null, null],
+            'empty' => ['', ''],
+            'blank' => [' ', ' '],
+            'string' => ['MyTestString', 'MyTestString'],
+            '<' => ['<', ''],
+            '>' => ['>', ''],
+            '<test>' => ['<test>', 'test'],
+            'Text1' => [' >>>This >>>text >>>should <<<look <<<different ', ' This text should look different ']
+        ];
+    }
+
+    //</editor-fold>
 }
diff --git a/test/unit/Resources/DummyHeidelpayResource.php b/test/unit/Resources/DummyHeidelpayResource.php
index 6e67a44a..a8125574 100755
--- a/test/unit/Resources/DummyHeidelpayResource.php
+++ b/test/unit/Resources/DummyHeidelpayResource.php
@@ -42,6 +42,9 @@ public function __construct(Customer $customer)
         $this->customer = $customer;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public function getLinkedResources(): array
     {
         return ['customer' => $this->customer];
diff --git a/test/unit/Resources/EmbeddedResources/BasketItemTest.php b/test/unit/Resources/EmbeddedResources/BasketItemTest.php
index 02577610..80fa2d17 100755
--- a/test/unit/Resources/EmbeddedResources/BasketItemTest.php
+++ b/test/unit/Resources/EmbeddedResources/BasketItemTest.php
@@ -65,6 +65,7 @@ public function settersAndGettersShouldWork()
         $basketItem->setTitle('myTitle');
         $basketItem->setSubTitle('mySubTitle');
         $basketItem->setImageUrl('https://my.image.url');
+        $basketItem->setType('myType');
 
         $this->assertEquals(2, $basketItem->getQuantity());
         $this->assertEquals(9876, $basketItem->getAmountDiscount());
@@ -77,6 +78,7 @@ public function settersAndGettersShouldWork()
         $this->assertEquals('myUnit', $basketItem->getUnit());
         $this->assertEquals('myTitle', $basketItem->getTitle());
         $this->assertEquals('mySubTitle', $basketItem->getSubTitle());
+        $this->assertEquals('myType', $basketItem->getType());
         $this->assertEquals('https://my.image.url', $basketItem->getImageUrl());
     }
 }
diff --git a/test/unit/Resources/KeypairTest.php b/test/unit/Resources/KeypairTest.php
index 41609064..d1d3f29a 100755
--- a/test/unit/Resources/KeypairTest.php
+++ b/test/unit/Resources/KeypairTest.php
@@ -26,26 +26,51 @@
 
 use heidelpayPHP\Resources\Keypair;
 use heidelpayPHP\test\BaseUnitTest;
+use PHPUnit\Framework\AssertionFailedError;
+use PHPUnit\Framework\Exception;
 use RuntimeException;
 use stdClass;
 
 class KeypairTest extends BaseUnitTest
 {
     /**
-     * Verify that an Authorization can be updated on handle response.
+     * Verify getters and setters work properly.
      *
      * @test
      *
-     * @throws RuntimeException
+     * @throws AssertionFailedError
+     * @throws Exception
      */
-    public function anAuthorizationShouldBeUpdatedThroughResponseHandling()
+    public function gettersAndSettersWorkAsExpected()
     {
         $keypair = new Keypair();
+        $this->assertFalse($keypair->isDetailed());
         $this->assertNull($keypair->getPublicKey());
         $this->assertNull($keypair->getPrivateKey());
-        /** @noinspection UnnecessaryAssertionInspection */
-        $this->assertInternalType('array', $keypair->getAvailablePaymentTypes());
-        $this->assertEmpty($keypair->getAvailablePaymentTypes());
+        $this->assertEmpty($keypair->getPaymentTypes());
+        $this->assertSame($keypair->getPaymentTypes(), $keypair->getAvailablePaymentTypes());
+        $this->assertNull($keypair->isCof());
+        $this->assertEquals('', $keypair->getSecureLevel());
+        $this->assertEquals('', $keypair->getMerchantName());
+        $this->assertEquals('', $keypair->getMerchantAddress());
+        $this->assertEquals('', $keypair->getAlias());
+        $this->assertFalse($keypair->isDetailed());
+
+        $keypair->setDetailed(true);
+
+        $this->assertTrue($keypair->isDetailed());
+    }
+
+    /**
+     * Verify that a key pair can be updated on handle response.
+     *
+     * @test
+     *
+     * @throws RuntimeException
+     */
+    public function aKeypairShouldBeUpdatedThroughResponseHandling()
+    {
+        $keypair = new Keypair();
 
         $paymentTypes = [
             'przelewy24',
@@ -67,8 +92,71 @@ public function anAuthorizationShouldBeUpdatedThroughResponseHandling()
         $testResponse->availablePaymentTypes = $paymentTypes;
 
         $keypair->handleResponse($testResponse);
-        $this->assertArraySubset($paymentTypes, $keypair->getAvailablePaymentTypes());
+        $this->assertArraySubset($paymentTypes, $keypair->getPaymentTypes());
+        $this->assertEquals('s-pub-1234', $keypair->getPublicKey());
+        $this->assertEquals('s-priv-4321', $keypair->getPrivateKey());
+    }
+
+    /**
+     * Verify that a key pair can be updated with details on handle response.
+     *
+     * @test
+     *
+     * @throws RuntimeException
+     */
+    public function aKeypairShouldBeUpdatedWithDetailsThroughResponseHandling()
+    {
+        $keypair = new Keypair();
+
+        $paymentTypes = [
+            (object) [
+                'supports' => [
+                    (object) [
+                        'brands' => ['JCB', 'VISAELECTRON', 'MAESTRO', 'VISA', 'MASTER'],
+                        'countries' => [],
+                        'channel' => '31HA07BC819430D3495C56BC18C55622',
+                        'currency' => ['CHF', 'CNY', 'JPY', 'USD', 'GBP', 'EUR']
+                    ]
+                ],
+                'type' => 'card',
+                'allowCustomerTypes' => 'B2C',
+                'allowCreditTransaction' => true,
+                '3ds' => true
+            ],
+            (object) [
+                'supports' => [
+                    (object) [
+                        'brands' => ['CUP', 'SOLO', 'CARTEBLEUE', 'VISAELECTRON', 'MAESTRO', 'AMEX', 'VISA', 'MASTER'],
+                        'countries' => [],
+                        'channel' => '31HA07BC819430D3495C7C9D07B1A922',
+                        'currency' => ['MGA', 'USD', 'GBP', 'EUR']
+                    ]
+                ],
+                'type' => 'card',
+                'allowCustomerTypes' => 'B2C',
+                'allowCreditTransaction' => true,
+                '3ds' => false
+            ]
+        ];
+
+        $testResponse = (object) [
+            'publicKey' => 's-pub-1234',
+            'privateKey' => 's-priv-4321',
+            'secureLevel' => 'SAQ-D',
+            'alias' => 'Readme.io user',
+            'merchantName' => 'Heidelpay GmbH',
+            'merchantAddress' => 'Vangerowstraße 18, 69115 Heidelberg',
+            'paymentTypes' => $paymentTypes
+            ];
+
+        $keypair->handleResponse($testResponse);
+        $this->assertEquals($paymentTypes, $keypair->getPaymentTypes());
+        $this->assertSame($keypair->getAvailablePaymentTypes(), $keypair->getPaymentTypes());
         $this->assertEquals('s-pub-1234', $keypair->getPublicKey());
         $this->assertEquals('s-priv-4321', $keypair->getPrivateKey());
+        $this->assertEquals('SAQ-D', $keypair->getSecureLevel());
+        $this->assertEquals('Readme.io user', $keypair->getAlias());
+        $this->assertEquals('Heidelpay GmbH', $keypair->getMerchantName());
+        $this->assertEquals('Vangerowstraße 18, 69115 Heidelberg', $keypair->getMerchantAddress());
     }
 }
diff --git a/test/unit/Resources/PaymentCancelTest.php b/test/unit/Resources/PaymentCancelTest.php
new file mode 100644
index 00000000..f1705583
--- /dev/null
+++ b/test/unit/Resources/PaymentCancelTest.php
@@ -0,0 +1,423 @@
+<?php
+/**
+ * This class defines unit tests to verify cancel functionality of the Payment resource.
+ *
+ * Copyright (C) 2019 heidelpay GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link  https://docs.heidelpay.com/
+ *
+ * @author  Simon Gabriel <development@heidelpay.com>
+ *
+ * @package  heidelpayPHP/test/unit
+ */
+namespace heidelpayPHP\test\unit\Resources;
+
+use heidelpayPHP\Constants\ApiResponseCodes;
+use heidelpayPHP\Exceptions\HeidelpayApiException;
+use heidelpayPHP\Resources\EmbeddedResources\Amount;
+use heidelpayPHP\Resources\Payment;
+use heidelpayPHP\Resources\TransactionTypes\Authorization;
+use heidelpayPHP\Resources\TransactionTypes\Cancellation;
+use heidelpayPHP\Resources\TransactionTypes\Charge;
+use heidelpayPHP\test\BaseUnitTest;
+use PHPUnit\Framework\AssertionFailedError;
+use PHPUnit\Framework\Exception;
+use PHPUnit\Framework\MockObject\MockObject;
+use ReflectionException;
+use RuntimeException;
+
+class PaymentCancelTest extends BaseUnitTest
+{
+    //<editor-fold desc="Deprecated since 1.2.3.0">
+
+    /**
+     * Verify payment:cancel calls cancelAllCharges and cancelAuthorizationAmount and returns first charge cancellation
+     * object.
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws ReflectionException
+     * @throws RuntimeException
+     *
+     * @deprecated since 1.2.3.0
+     */
+    public function cancelShouldCallCancelAllChargesAndCancelAuthorizationAndReturnFirstChargeCancellationObject()
+    {
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['cancelAmount'])->getMock();
+        $cancellation = new Cancellation(1.0);
+        $paymentMock->expects($this->once())->method('cancelAmount')->willReturn([$cancellation]);
+
+        /** @var Payment $paymentMock */
+        $this->assertSame($cancellation, $paymentMock->cancel());
+    }
+
+    /**
+     * Verify payment:cancel throws Exception if no cancellation and no auth existed to be cancelled.
+     *
+     * @test
+     *
+     * @throws ReflectionException
+     * @throws RuntimeException
+     * @throws HeidelpayApiException
+     *
+     * @deprecated since 1.2.3.0
+     */
+    public function cancelShouldThrowExceptionIfNoTransactionExistsToBeCancelled()
+    {
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['cancelAllCharges', 'cancelAuthorization'])->getMock();
+
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessage('This Payment could not be cancelled.');
+
+        /** @var Payment $paymentMock */
+        $paymentMock->cancel();
+    }
+
+    /**
+     * Verify cancel all charges will call cancel on each existing charge of the payment and will return a list of
+     * cancels and exceptions.
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws ReflectionException
+     * @throws RuntimeException
+     *
+     * @deprecated since 1.2.3.0
+     */
+    public function cancelAllChargesShouldCallCancelOnAllChargesAndReturnCancelsAndExceptions()
+    {
+        $cancellation1 = new Cancellation(1.0);
+        $cancellation2 = new Cancellation(2.0);
+        $cancellation3 = new Cancellation(3.0);
+        $exception1 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_ALREADY_CHARGED_BACK);
+        $exception2 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_ALREADY_CHARGED_BACK);
+
+        $chargeMock1 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+        $chargeMock1->expects($this->once())->method('cancel')->willReturn($cancellation1);
+
+        $chargeMock2 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+        $chargeMock2->expects($this->once())->method('cancel')->willThrowException($exception1);
+
+        $chargeMock3 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+        $chargeMock3->expects($this->once())->method('cancel')->willReturn($cancellation2);
+
+        $chargeMock4 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+        $chargeMock4->expects($this->once())->method('cancel')->willThrowException($exception2);
+
+        $chargeMock5 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+        $chargeMock5->expects($this->once())->method('cancel')->willReturn($cancellation3);
+
+        /**
+         * @var Charge $chargeMock1
+         * @var Charge $chargeMock2
+         * @var Charge $chargeMock3
+         * @var Charge $chargeMock4
+         * @var Charge $chargeMock5
+         */
+        $payment = new Payment();
+        $payment->addCharge($chargeMock1)->addCharge($chargeMock2)->addCharge($chargeMock3)->addCharge($chargeMock4)->addCharge($chargeMock5);
+
+        list($cancellations, $exceptions) = $payment->cancelAllCharges();
+        $this->assertArraySubset([$cancellation1, $cancellation2, $cancellation3], $cancellations);
+        $this->assertArraySubset([$exception1, $exception2], $exceptions);
+    }
+
+    /**
+     * Verify cancelAllCharges will throw any exception with Code different to
+     * ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CANCELED.
+     *
+     * @test
+     *
+     * @throws ReflectionException
+     * @throws RuntimeException
+     *
+     * @deprecated since 1.2.3.0
+     */
+    public function cancelAllChargesShouldThrowChargeCancelExceptionsOtherThanAlreadyCharged()
+    {
+        $ex1 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_ALREADY_CHARGED_BACK);
+        $ex2 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGED_AMOUNT_HIGHER_THAN_EXPECTED);
+
+        $chargeMock1 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+        $chargeMock1->expects($this->once())->method('cancel')->willThrowException($ex1);
+
+        $chargeMock2 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+        $chargeMock2->expects($this->once())->method('cancel')->willThrowException($ex2);
+
+        /**
+         * @var Charge $chargeMock1
+         * @var Charge $chargeMock2
+         */
+        $payment = (new Payment())->addCharge($chargeMock1)->addCharge($chargeMock2);
+
+        try {
+            $payment->cancelAllCharges();
+            $this->assertFalse(true, 'The expected exception has not been thrown.');
+        } catch (HeidelpayApiException $e) {
+            $this->assertSame($ex2, $e);
+        }
+    }
+
+    //</editor-fold>
+
+    /**
+     * Verify cancelAmount will call cancelAuthorizationAmount with the amountToCancel.
+     * When cancelAmount is <= the value of the cancellation it Will return auth cancellation only.
+     * Charge cancel will not be called if the amount to cancel has been cancelled on the authorization.
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws ReflectionException
+     * @throws RuntimeException
+     * @throws Exception
+     * @throws \PHPUnit\Framework\MockObject\RuntimeException
+     */
+    public function cancelAmountShouldCallCancelAuthorizationAmount()
+    {
+        /** @var MockObject|Payment $paymentMock */
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['cancelAuthorizationAmount'])->getMock();
+        $chargeMock = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
+
+        $paymentMock->setAuthorization((new Authorization(12.3))->setPayment($paymentMock));
+
+        $cancellation = new Cancellation(12.3);
+        $paymentMock->expects($this->exactly(2))->method('cancelAuthorizationAmount')->willReturn($cancellation);
+        $chargeMock->expects($this->never())->method('cancel');
+
+        $this->assertEquals([$cancellation], $paymentMock->cancelAmount(10.0));
+        $this->assertEquals([$cancellation], $paymentMock->cancelAmount(12.3));
+    }
+
+    /**
+     * Verify that cancel amount will be cancelled on charges if auth does not exist.
+     *
+     * @test
+     *
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws ReflectionException
+     * @throws RuntimeException
+     * @throws \PHPUnit\Framework\MockObject\RuntimeException
+     */
+    public function chargesShouldBeCancelledIfAuthDoesNotExist1()
+    {
+        /** @var MockObject|Payment $paymentMock */
+        /** @var MockObject|Charge $chargeMock */
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['cancelAuthorizationAmount'])->getMock();
+        $chargeMock = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->setConstructorArgs([10.0])->getMock();
+
+        $cancellation = new Cancellation(10.0);
+
+        $paymentMock->expects($this->once())->method('cancelAuthorizationAmount')->willReturn(null);
+        $chargeMock->expects($this->once())->method('cancel')->with(10.0, 'CANCEL')->willReturn($cancellation);
+        $paymentMock->addCharge($chargeMock);
+
+        $this->assertEquals([$cancellation], $paymentMock->cancelAmount(10.0));
+    }
+
+    /**
+     * Verify that cancel amount will be cancelled on charges if auth does not exist.
+     *
+     * @test
+     *
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws ReflectionException
+     * @throws RuntimeException
+     * @throws \PHPUnit\Framework\MockObject\RuntimeException
+     */
+    public function chargesShouldBeCancelledIfAuthDoesNotExist2()
+    {
+        /** @var MockObject|Payment $paymentMock */
+        /** @var MockObject|Charge $charge1Mock */
+        /** @var MockObject|Charge $charge2Mock */
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['cancelAuthorizationAmount'])->getMock();
+        $charge1Mock = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->setConstructorArgs([10.0])->getMock();
+        $charge2Mock = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->setConstructorArgs([12.3])->getMock();
+
+        $cancellation1 = new Cancellation(10.0);
+        $cancellation2 = new Cancellation(2.3);
+
+        $paymentMock->expects($this->exactly(3))->method('cancelAuthorizationAmount')->willReturn(null);
+        $charge1Mock->expects($this->exactly(3))->method('cancel')->withConsecutive([10.0, 'CANCEL'], [null, 'CANCEL'], [null, 'CANCEL'])->willReturn($cancellation1);
+        $charge2Mock->expects($this->exactly(2))->method('cancel')->withConsecutive([2.3, 'CANCEL'], [null, 'CANCEL'])->willReturn($cancellation2);
+
+        $paymentMock->addCharge($charge1Mock)->addCharge($charge2Mock);
+
+        $this->assertEquals([$cancellation1], $paymentMock->cancelAmount(10.0));
+        $this->assertEquals([$cancellation1, $cancellation2], $paymentMock->cancelAmount(12.3));
+        $this->assertEquals([$cancellation1, $cancellation2], $paymentMock->cancelAmount());
+    }
+
+    /**
+     * Verify certain errors are allowed during cancellation and will be ignored.
+     *
+     * @test
+     * @dataProvider allowedErrorCodesDuringChargeCancel
+     *
+     * @param string $allowedExceptionCode
+     * @param bool   $shouldHaveThrownException
+     *
+     * @throws Exception
+     * @throws ReflectionException
+     * @throws RuntimeException
+     * @throws AssertionFailedError
+     * @throws \PHPUnit\Framework\MockObject\RuntimeException
+     */
+    public function verifyAllowedErrorsWillBeIgnoredDuringChargeCancel($allowedExceptionCode, $shouldHaveThrownException)
+    {
+        /** @var MockObject|Payment $paymentMock */
+        /** @var MockObject|Charge $chargeMock */
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['cancelAuthorizationAmount'])->getMock();
+        $chargeMock = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->disableOriginalConstructor()->getMock();
+
+        $allowedException = new HeidelpayApiException(null, null, $allowedExceptionCode);
+        $chargeMock->method('cancel')->willThrowException($allowedException);
+        $paymentMock->addCharge($chargeMock);
+
+        try {
+            $this->assertEquals([], $paymentMock->cancelAmount(12.3));
+            $this->assertFalse($shouldHaveThrownException, 'Exception should have been thrown here!');
+        } catch (HeidelpayApiException $e) {
+            $this->assertTrue($shouldHaveThrownException, "Exception should not have been thrown here! ({$e->getCode()})");
+        }
+    }
+
+    /**
+     * Verify cancelAuthorizationAmount will call cancel on the authorization and will return a list of cancels.
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws ReflectionException
+     * @throws RuntimeException
+     */
+    public function cancelAuthorizationAmountShouldCallCancelOnTheAuthorizationAndReturnCancellation()
+    {
+        $cancellation = new Cancellation(1.0);
+        $authorizationMock = $this->getMockBuilder(Authorization::class)->setMethods(['cancel'])->getMock();
+        $authorizationMock->expects($this->once())->method('cancel')->willReturn($cancellation);
+
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['getAuthorization'])->getMock();
+        $paymentMock->expects($this->once())->method('getAuthorization')->willReturn($authorizationMock);
+
+        /**
+         * @var Authorization $authorizationMock
+         * @var Payment       $paymentMock
+         */
+        $paymentMock->setAuthorization($authorizationMock);
+        $this->assertEquals($cancellation, $paymentMock->cancelAuthorizationAmount());
+    }
+
+    /**
+     * Verify cancelAuthorizationAmount will call cancel the given amount on the authorization of the payment.
+     * Cancellation amount will be the remaining amount of the payment at max.
+     *
+     * @test
+     *
+     * @throws Exception
+     * @throws HeidelpayApiException
+     * @throws ReflectionException
+     * @throws RuntimeException
+     * @throws \PHPUnit\Framework\MockObject\RuntimeException
+     */
+    public function cancelAuthorizationAmountShouldCallCancelWithTheRemainingAmountAtMax()
+    {
+        $cancellation = new Cancellation();
+
+        /** @var MockObject|Authorization $authorizationMock */
+        $authorizationMock = $this->getMockBuilder(Authorization::class)->setConstructorArgs([100.0])->setMethods(['cancel'])->getMock();
+        $authorizationMock->expects($this->exactly(4))->method('cancel')->withConsecutive([null], [50.0], [100.0], [100.0])->willReturn($cancellation);
+
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['getAuthorization', 'getAmount'])->getMock();
+        $paymentMock->method('getAmount')->willReturn((new Amount())->setRemaining(100.0));
+        $paymentMock->expects($this->exactly(4))->method('getAuthorization')->willReturn($authorizationMock);
+
+        /**
+         * @var Authorization $authorizationMock
+         * @var Payment       $paymentMock
+         */
+        $paymentMock->setAuthorization($authorizationMock);
+        $this->assertEquals($cancellation, $paymentMock->cancelAuthorizationAmount());
+        $this->assertEquals($cancellation, $paymentMock->cancelAuthorizationAmount(50.0));
+        $this->assertEquals($cancellation, $paymentMock->cancelAuthorizationAmount(100.0));
+        $this->assertEquals($cancellation, $paymentMock->cancelAuthorizationAmount(101.0));
+    }
+
+    /**
+     * Verify certain errors are allowed during cancellation and will be ignored.
+     *
+     * @test
+     * @dataProvider allowedErrorCodesDuringAuthCancel
+     *
+     * @param string $allowedExceptionCode
+     * @param bool   $shouldHaveThrownException
+     *
+     * @throws Exception
+     * @throws ReflectionException
+     * @throws RuntimeException
+     * @throws AssertionFailedError
+     * @throws \PHPUnit\Framework\MockObject\RuntimeException
+     */
+    public function verifyAllowedErrorsWillBeIgnoredDuringAuthorizeCancel($allowedExceptionCode, $shouldHaveThrownException)
+    {
+        /** @var MockObject|Payment $paymentMock */
+        /** @var MockObject|Authorization $authMock */
+        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['getAuthorization'])->getMock();
+        $authMock = $this->getMockBuilder(Authorization::class)->setMethods(['cancel'])->disableOriginalConstructor()->getMock();
+
+        $allowedException = new HeidelpayApiException(null, null, $allowedExceptionCode);
+        $authMock->method('cancel')->willThrowException($allowedException);
+        $paymentMock->method('getAuthorization')->willReturn($authMock);
+
+        try {
+            $this->assertEquals(null, $paymentMock->cancelAuthorizationAmount(12.3));
+            $this->assertFalse($shouldHaveThrownException, 'Exception should have been thrown here!');
+        } catch (HeidelpayApiException $e) {
+            $this->assertTrue($shouldHaveThrownException, "Exception should not have been thrown here! ({$e->getCode()})");
+        }
+    }
+
+    //<editor-fold desc="Data Providers">
+
+    /**
+     * @return array
+     */
+    public function allowedErrorCodesDuringChargeCancel(): array
+    {
+        return [
+            'already cancelled' => [ApiResponseCodes::API_ERROR_ALREADY_CANCELLED, false],
+            'already chargedBack' => [ApiResponseCodes::API_ERROR_ALREADY_CANCELLED, false],
+            'other' => [ApiResponseCodes::API_ERROR_BASKET_ITEM_IMAGE_INVALID_EXTENSION, true]
+        ];
+    }
+
+    /**
+     * @return array
+     */
+    public function allowedErrorCodesDuringAuthCancel(): array
+    {
+        return [
+            'already cancelled' => [ApiResponseCodes::API_ERROR_ALREADY_CANCELLED, false],
+            'already chargedBack' => [ApiResponseCodes::API_ERROR_ALREADY_CHARGED, false],
+            'other' => [ApiResponseCodes::API_ERROR_BASKET_ITEM_IMAGE_INVALID_EXTENSION, true]
+        ];
+    }
+
+    //</editor-fold>
+}
diff --git a/test/unit/Resources/PaymentTest.php b/test/unit/Resources/PaymentTest.php
index ecc81841..b5b30833 100755
--- a/test/unit/Resources/PaymentTest.php
+++ b/test/unit/Resources/PaymentTest.php
@@ -24,7 +24,6 @@
  */
 namespace heidelpayPHP\test\unit\Resources;
 
-use heidelpayPHP\Constants\ApiResponseCodes;
 use heidelpayPHP\Constants\PaymentState;
 use heidelpayPHP\Exceptions\HeidelpayApiException;
 use heidelpayPHP\Heidelpay;
@@ -788,15 +787,35 @@ public function handleResponseShouldFetchAndUpdatePaymentTypeIfTheIdIsSet()
      */
     public function handleResponseShouldFetchAndUpdateMetadataIfTheIdIsSet()
     {
-        $payment = (new Payment())->setId('myPaymentId');
-
-        $resourceServiceMock = $this->getMockBuilder(ResourceService::class)
-            ->disableOriginalConstructor()->setMethods(['fetchMetadata'])->getMock();
+        $resourceServiceMock = $this->getMockBuilder(ResourceService::class)->disableOriginalConstructor()->setMethods(['fetchMetadata'])->getMock();
         $resourceServiceMock->expects($this->once())->method('fetchMetadata')->with('MetadataId');
+        /** @var ResourceService $resourceServiceMock */
+        $heidelpayObj = (new Heidelpay('s-priv-123'))->setResourceService($resourceServiceMock);
+        $payment = (new Payment())->setId('myPaymentId')->setParentResource($heidelpayObj);
+
+        $response = new stdClass();
+        $response->resources = new stdClass();
+        $response->resources->metadataId = 'MetadataId';
+        $payment->handleResponse($response);
+    }
 
+    /**
+     * Verify handleResponse updates metadata.
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     * @throws ReflectionException
+     */
+    public function handleResponseShouldGetMetadataIfUnfetchedMetadataObjectWithIdIsGiven()
+    {
+        $metadata = (new Metadata())->setId('MetadataId');
+        $resourceServiceMock = $this->getMockBuilder(ResourceService::class)->disableOriginalConstructor()->setMethods(['getResource'])->getMock();
+        $resourceServiceMock->expects($this->once())->method('getResource')->with($metadata);
         /** @var ResourceService $resourceServiceMock */
         $heidelpayObj = (new Heidelpay('s-priv-123'))->setResourceService($resourceServiceMock);
-        $payment->setParentResource($heidelpayObj);
+        $payment = (new Payment())->setId('myPaymentId')->setParentResource($heidelpayObj)->setMetadata($metadata);
 
         $response = new stdClass();
         $response->resources = new stdClass();
@@ -1276,315 +1295,6 @@ public function handleResponseShouldAddPayoutFromResponseIfItDoesNotExists()
 
     //</editor-fold>
 
-    //<editor-fold desc="Cancel">
-
-    /**
-     * Verify payment:cancel calls cancelAllCharges and cancelAuthorization and returns first charge cancellation
-     * object.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelShouldCallCancelAllChargesAndCancelAuthorizationAndReturnFirstChargeCancellationObject()
-    {
-        $paymentMock = $this->getMockBuilder(Payment::class)
-            ->setMethods(['cancelAllCharges', 'cancelAuthorization'])->getMock();
-        $cancellation = new Cancellation(1.0);
-        $exception1 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $exception2 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $exception3 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $paymentMock->expects($this->once())->method('cancelAllCharges')
-            ->willReturn([[$cancellation], [$exception1, $exception2]]);
-        $paymentMock->expects($this->once())->method('cancelAuthorization')->willReturn([[], [$exception3]]);
-
-        /** @var Payment $paymentMock */
-        $this->assertSame($cancellation, $paymentMock->cancel());
-    }
-
-    /**
-     * Verify payment:cancel calls cancelAllCharges and cancelAuthorization and returns authorize cancellation object if
-     * no charge cancellation object exist.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelShouldReturnAuthorizationCancellationObjectIfNoChargeCancellationsExist()
-    {
-        $paymentMock = $this->getMockBuilder(Payment::class)
-            ->setMethods(['cancelAllCharges', 'cancelAuthorization'])->getMock();
-        $cancellation = new Cancellation(2.0);
-        $exception1 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $exception2 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $exception3 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $paymentMock->expects($this->once())->method('cancelAllCharges')
-            ->willReturn([[], [$exception1, $exception2]]);
-        $paymentMock->expects($this->once())->method('cancelAuthorization')
-            ->willReturn([[$cancellation], [$exception3]]);
-
-        /** @var Payment $paymentMock */
-        $this->assertSame($cancellation, $paymentMock->cancel());
-    }
-
-    /**
-     * Verify payment:cancel throws first charge exception if all charges and auth have already been cancelled.
-     *
-     * @test
-     *
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelShouldThrowFirstChargeAlreadyCancelledExceptionIfNoCancellationTookPlace()
-    {
-        $paymentMock = $this->getMockBuilder(Payment::class)
-            ->setMethods(['cancelAllCharges', 'cancelAuthorization'])->getMock();
-        $exception1 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $exception2 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $exception3 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $paymentMock->expects($this->once())->method('cancelAllCharges')->willReturn([[], [$exception1, $exception2]]);
-        $paymentMock->expects($this->once())->method('cancelAuthorization')->willReturn([[], [$exception3]]);
-
-        try {
-            /** @var Payment $paymentMock */
-            $paymentMock->cancel();
-            $this->assertFalse(true, 'The expected exception has not been thrown.');
-        } catch (HeidelpayApiException $e) {
-            $this->assertSame($exception1, $e);
-        }
-    }
-
-    /**
-     * Verify payment:cancel throws auth exception if no charges existed and the authorization was already cancelled.
-     *
-     * @test
-     *
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelShouldThrowAuthAlreadyCancelledExceptionIfNoCancellationTookPlaceAndNoChargeExceptionExists()
-    {
-        $paymentMock = $this->getMockBuilder(Payment::class)
-            ->setMethods(['cancelAllCharges', 'cancelAuthorization'])->getMock();
-        $exception = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $paymentMock->expects($this->once())->method('cancelAllCharges')->willReturn([[], []]);
-        $paymentMock->expects($this->once())->method('cancelAuthorization')->willReturn([[], [$exception]]);
-
-        try {
-            /** @var Payment $paymentMock */
-            $paymentMock->cancel();
-            $this->assertFalse(true, 'The expected exception has not been thrown.');
-        } catch (HeidelpayApiException $e) {
-            $this->assertSame($exception, $e);
-        }
-    }
-
-    /**
-     * Verify payment:cancel throws Exception if no cancellation and no auth existed to be cancelled.
-     *
-     * @test
-     *
-     * @throws ReflectionException
-     * @throws RuntimeException
-     * @throws HeidelpayApiException
-     */
-    public function cancelShouldThrowExceptionIfNoTransactionExistsToBeCancelled()
-    {
-        $paymentMock = $this->getMockBuilder(Payment::class)
-            ->setMethods(['cancelAllCharges', 'cancelAuthorization'])->getMock();
-        $paymentMock->expects($this->once())->method('cancelAllCharges')->willReturn([[], []]);
-        $paymentMock->expects($this->once())->method('cancelAuthorization')->willReturn([[], []]);
-
-        $this->expectException(RuntimeException::class);
-        $this->expectExceptionMessage('This Payment could not be cancelled.');
-
-        /** @var Payment $paymentMock */
-        $paymentMock->cancel();
-    }
-
-    /**
-     * Verify cancel all charges will call cancel on each existing charge of the payment and will return a list of
-     * cancels and exceptions.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelAllChargesShouldCallCancelOnAllChargesAndReturnCancelsAndExceptions()
-    {
-        $cancellation1 = new Cancellation(1.0);
-        $cancellation2 = new Cancellation(2.0);
-        $cancellation3 = new Cancellation(3.0);
-        $exception1 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $exception2 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-
-        $chargeMock1 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
-        $chargeMock1->expects($this->once())->method('cancel')->willReturn($cancellation1);
-
-        $chargeMock2 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
-        $chargeMock2->expects($this->once())->method('cancel')->willThrowException($exception1);
-
-        $chargeMock3 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
-        $chargeMock3->expects($this->once())->method('cancel')->willReturn($cancellation2);
-
-        $chargeMock4 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
-        $chargeMock4->expects($this->once())->method('cancel')->willThrowException($exception2);
-
-        $chargeMock5 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
-        $chargeMock5->expects($this->once())->method('cancel')->willReturn($cancellation3);
-
-        /**
-         * @var Charge $chargeMock1
-         * @var Charge $chargeMock2
-         * @var Charge $chargeMock3
-         * @var Charge $chargeMock4
-         * @var Charge $chargeMock5
-         */
-        $payment = new Payment();
-        $payment->addCharge($chargeMock1)
-                ->addCharge($chargeMock2)
-                ->addCharge($chargeMock3)
-                ->addCharge($chargeMock4)
-                ->addCharge($chargeMock5);
-
-        list($cancellations, $exceptions) = $payment->cancelAllCharges();
-        $this->assertArraySubset([$cancellation1, $cancellation2, $cancellation3], $cancellations);
-        $this->assertArraySubset([$exception1, $exception2], $exceptions);
-    }
-
-    /**
-     * Verify cancelAllCharges will throw any erxception with Code different to
-     * ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CANCELED.
-     *
-     * @test
-     *
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelAllChargesShouldThrowChargeCancelExceptionsOtherThanAlreadyCharged()
-    {
-        $ex1 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGE_ALREADY_CHARGED_BACK);
-        $ex2 = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGED_AMOUNT_HIGHER_THAN_EXPECTED);
-
-        $chargeMock1 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
-        $chargeMock1->expects($this->once())->method('cancel')->willThrowException($ex1);
-
-        $chargeMock2 = $this->getMockBuilder(Charge::class)->setMethods(['cancel'])->getMock();
-        $chargeMock2->expects($this->once())->method('cancel')->willThrowException($ex2);
-
-        /**
-         * @var Charge $chargeMock1
-         * @var Charge $chargeMock2
-         */
-        $payment = (new Payment())->addCharge($chargeMock1)->addCharge($chargeMock2);
-
-        try {
-            $payment->cancelAllCharges();
-            $this->assertFalse(true, 'The expected exception has not been thrown.');
-        } catch (HeidelpayApiException $e) {
-            $this->assertSame($ex2, $e);
-        }
-    }
-
-    /**
-     * Verify cancelAuthorization will call cancel on the authorization and will return a list of cancels.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelAuthorizationShouldCallCancelOnTheAuthorizationAndReturnCancels()
-    {
-        $cancellation = new Cancellation(1.0);
-        $authorizationMock = $this->getMockBuilder(Authorization::class)->setMethods(['cancel'])->getMock();
-        $authorizationMock->expects($this->once())->method('cancel')->willReturn($cancellation);
-
-        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['getAuthorization'])->getMock();
-        $paymentMock->expects($this->once())->method('getAuthorization')->willReturn($authorizationMock);
-
-        /**
-         * @var Authorization $authorizationMock
-         * @var Payment       $paymentMock
-         */
-        $paymentMock->setAuthorization($authorizationMock);
-        list($cancellations, $exceptions) = $paymentMock->cancelAuthorization();
-        $this->assertArraySubset([$cancellation], $cancellations);
-        $this->assertIsEmptyArray($exceptions);
-    }
-
-    /**
-     * Verify cancelAuthorization will call cancel on the authorization and will return a list of exceptions.
-     *
-     * @test
-     *
-     * @throws HeidelpayApiException
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelAuthorizationShouldCallCancelOnTheAuthorizationAndReturnExceptions()
-    {
-        $exception = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_AUTHORIZE_ALREADY_CANCELLED);
-
-        $authorizationMock = $this->getMockBuilder(Authorization::class)->setMethods(['cancel'])->getMock();
-        $authorizationMock->expects($this->once())->method('cancel')->willThrowException($exception);
-
-        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['getAuthorization'])->getMock();
-        $paymentMock->expects($this->once())->method('getAuthorization')->willReturn($authorizationMock);
-
-        /**
-         * @var Authorization $authorizationMock
-         * @var Payment       $paymentMock
-         */
-        $paymentMock->setAuthorization($authorizationMock);
-        list($cancellations, $exceptions) = $paymentMock->cancelAuthorization();
-
-        $this->assertIsEmptyArray($cancellations);
-        $this->assertArraySubset([$exception], $exceptions);
-    }
-
-    /**
-     * Verify cancelAuthorization will throw any exception with Code different to
-     * ApiResponseCodes::API_ERROR_AUTHORIZATION_ALREADY_CANCELED.
-     *
-     * @test
-     *
-     * @throws ReflectionException
-     * @throws RuntimeException
-     */
-    public function cancelAllChargesShouldThrowAuthorizationCancelExceptionsOtherThanAlreadyCharged()
-    {
-        $exception = new HeidelpayApiException('', '', ApiResponseCodes::API_ERROR_CHARGED_AMOUNT_HIGHER_THAN_EXPECTED);
-
-        $authorizationMock = $this->getMockBuilder(Authorization::class)->setMethods(['cancel'])->getMock();
-        $authorizationMock->expects($this->once())->method('cancel')->willThrowException($exception);
-
-        $paymentMock = $this->getMockBuilder(Payment::class)->setMethods(['getAuthorization'])->getMock();
-        $paymentMock->expects($this->once())->method('getAuthorization')->willReturn($authorizationMock);
-
-        /**
-         * @var Authorization $authorizationMock
-         * @var Payment       $paymentMock
-         */
-        $paymentMock->setAuthorization($authorizationMock);
-
-        try {
-            $paymentMock->cancelAuthorization();
-            $this->assertFalse(true, 'The expected exception has not been thrown.');
-        } catch (HeidelpayApiException $e) {
-            $this->assertSame($exception, $e);
-        }
-    }
-
-    //</editor-fold>
-
     /**
      * Verify charge will call chargePayment on heidelpay object.
      *
@@ -1649,8 +1359,7 @@ public function setMetaDataShouldSetParentResourceAndCreateMetaDataObject()
     {
         $metadata = (new Metadata())->addMetadata('myData', 'myValue');
 
-        $resourceSrvMock = $this->getMockBuilder(ResourceService::class)->setMethods(['create'])
-            ->disableOriginalConstructor()->getMock();
+        $resourceSrvMock = $this->getMockBuilder(ResourceService::class)->setMethods(['create'])->disableOriginalConstructor()->getMock();
         $resourceSrvMock->expects($this->once())->method('create')->with($metadata);
 
         /** @var ResourceService $resourceSrvMock */
@@ -1669,6 +1378,43 @@ public function setMetaDataShouldSetParentResourceAndCreateMetaDataObject()
         $this->assertSame($heidelpay, $metadata->getParentResource());
     }
 
+    /**
+     * Verify setMetadata will not set the metadata property if it is not of type metadata.
+     *
+     * @test
+     *
+     * @throws HeidelpayApiException
+     * @throws RuntimeException
+     * @throws ReflectionException
+     */
+    public function metadataMustBeOfTypeMetadata()
+    {
+        $metadata = new Metadata();
+        $resourceSrvMock = $this->getMockBuilder(ResourceService::class)->setMethods(['create'])->disableOriginalConstructor()->getMock();
+        $resourceSrvMock->expects($this->once())->method('create')->with($metadata);
+        /** @var ResourceService $resourceSrvMock */
+        $heidelpay = (new Heidelpay('s-priv-1234'))->setResourceService($resourceSrvMock);
+
+        // when
+        $payment = new Payment($heidelpay);
+
+        // then
+        $this->assertNull($payment->getMetadata());
+
+        // when
+        /** @noinspection PhpParamsInspection */
+        $payment->setMetadata('test');
+
+        // then
+        $this->assertNull($payment->getMetadata());
+
+        // when
+        $payment->setMetadata($metadata);
+
+        // then
+        $this->assertSame($metadata, $payment->getMetadata());
+    }
+
     /**
      * Verify set Basket will call create if the given basket object does not exist yet.
      *
diff --git a/test/unit/Resources/TransactionTypes/AuthorizationTest.php b/test/unit/Resources/TransactionTypes/AuthorizationTest.php
index f03fa8de..3ba3f186 100755
--- a/test/unit/Resources/TransactionTypes/AuthorizationTest.php
+++ b/test/unit/Resources/TransactionTypes/AuthorizationTest.php
@@ -2,7 +2,7 @@
 /**
  * This class defines unit tests to verify functionality of the Authorization transaction type.
  *
- * Copyright (C) 2018 heidelpay GmbH
+ * Copyright (C) 2019 heidelpay GmbH
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -54,19 +54,22 @@ public function gettersAndSettersShouldWorkProperly()
         $this->assertNull($authorization->getCurrency());
         $this->assertNull($authorization->getReturnUrl());
         $this->assertNull($authorization->isCard3ds());
+        $this->assertNull($authorization->getPaymentReference());
 
         $authorization = new Authorization(123.4, 'myCurrency', 'https://my-return-url.test');
-        $authorization->setCard3ds(true);
+        $authorization->setCard3ds(true)->setPaymentReference('my payment reference');
         $this->assertEquals(123.4, $authorization->getAmount());
         $this->assertEquals('myCurrency', $authorization->getCurrency());
         $this->assertEquals('https://my-return-url.test', $authorization->getReturnUrl());
+        $this->assertEquals('my payment reference', $authorization->getPaymentReference());
         $this->assertTrue($authorization->isCard3ds());
 
         $authorization->setAmount(567.8)->setCurrency('myNewCurrency')->setReturnUrl('https://another-return-url.test');
-        $authorization->setCard3ds(false);
+        $authorization->setCard3ds(false)->setPaymentReference('different payment reference');
         $this->assertEquals(567.8, $authorization->getAmount());
         $this->assertEquals('myNewCurrency', $authorization->getCurrency());
         $this->assertEquals('https://another-return-url.test', $authorization->getReturnUrl());
+        $this->assertEquals('different payment reference', $authorization->getPaymentReference());
         $this->assertFalse($authorization->isCard3ds());
     }
 
@@ -221,6 +224,30 @@ public function chargeShouldCallChargeAuthorizationOnHeidelpayObject()
         $authorization->charge(321.9);
     }
 
+    /**
+     * Verify getter for cancelled amount.
+     *
+     * @test
+     *
+     * @throws Exception
+     */
+    public function getCancelledAmountReturnsTheCancelledAmount()
+    {
+        $authorization = new Authorization();
+        $this->assertEquals(0.0, $authorization->getCancelledAmount());
+
+        $authorization = new Authorization(123.4, 'myCurrency', 'https://my-return-url.test');
+        $this->assertEquals(0.0, $authorization->getCancelledAmount());
+
+        $cancellation1 = new Cancellation(10.0);
+        $authorization->addCancellation($cancellation1);
+        $this->assertEquals(10.0, $authorization->getCancelledAmount());
+
+        $cancellation2 = new Cancellation(10.0);
+        $authorization->addCancellation($cancellation2);
+        $this->assertEquals(20.0, $authorization->getCancelledAmount());
+    }
+
     //<editor-fold desc="Data Providers">
 
     /**
diff --git a/test/unit/Resources/TransactionTypes/ChargeTest.php b/test/unit/Resources/TransactionTypes/ChargeTest.php
index 506d8250..f524411b 100755
--- a/test/unit/Resources/TransactionTypes/ChargeTest.php
+++ b/test/unit/Resources/TransactionTypes/ChargeTest.php
@@ -33,6 +33,7 @@
 use heidelpayPHP\Resources\TransactionTypes\Charge;
 use heidelpayPHP\test\BaseUnitTest;
 use PHPUnit\Framework\Exception;
+use PHPUnit\Framework\MockObject\MockObject;
 use ReflectionException;
 use RuntimeException;
 use stdClass;
@@ -185,4 +186,53 @@ public function cancelShouldCallCancelChargeOnHeidelpayObject()
         $charge->cancel();
         $charge->cancel(321.9);
     }
+
+    /**
+     * Verify getter for cancelled amount.
+     *
+     * @test
+     *
+     * @throws Exception
+     */
+    public function getCancelledAmountReturnsTheCancelledAmount()
+    {
+        $charge = new Charge();
+        $this->assertEquals(0.0, $charge->getCancelledAmount());
+
+        $charge = new Charge(123.4, 'myCurrency', 'https://my-return-url.test');
+        $this->assertEquals(0.0, $charge->getCancelledAmount());
+
+        $cancellation1 = new Cancellation(10.0);
+        $charge->addCancellation($cancellation1);
+        $this->assertEquals(10.0, $charge->getCancelledAmount());
+
+        $cancellation2 = new Cancellation(10.0);
+        $charge->addCancellation($cancellation2);
+        $this->assertEquals(20.0, $charge->getCancelledAmount());
+    }
+
+    /**
+     * Verify getter for total amount.
+     *
+     * @test
+     *
+     * @throws Exception
+     * @throws ReflectionException
+     * @throws \PHPUnit\Framework\MockObject\RuntimeException
+     */
+    public function getTotalAmountReturnsAmountMinusCancelledAmount()
+    {
+        /** @var MockObject|Charge $chargeMock */
+        $chargeMock = $this->getMockBuilder(Charge::class)
+            ->setMethods(['getCancelledAmount'])
+            ->setConstructorArgs([123.4, 'myCurrency', 'https://my-return-url.test'])
+            ->getMock();
+
+        $chargeMock->expects($this->exactly(3))->method('getCancelledAmount')
+            ->willReturnOnConsecutiveCalls(0.0, 100.0, 123.4);
+
+        $this->assertEquals(123.4, $chargeMock->getTotalAmount());
+        $this->assertEquals(23.4, $chargeMock->getTotalAmount());
+        $this->assertEquals(0.0, $chargeMock->getTotalAmount());
+    }
 }
diff --git a/test/unit/Resources/TransactionTypes/PayoutTest.php b/test/unit/Resources/TransactionTypes/PayoutTest.php
index 3fa655a0..c144c785 100644
--- a/test/unit/Resources/TransactionTypes/PayoutTest.php
+++ b/test/unit/Resources/TransactionTypes/PayoutTest.php
@@ -49,16 +49,21 @@ public function gettersAndSettersShouldWorkProperly()
         $this->assertNull($payout->getAmount());
         $this->assertNull($payout->getCurrency());
         $this->assertNull($payout->getReturnUrl());
+        $this->assertNull($payout->getPaymentReference());
 
         $payout = new Payout(123.4, 'myCurrency', 'https://my-return-url.test');
+        $payout->setPaymentReference('my payment reference');
         $this->assertEquals(123.4, $payout->getAmount());
         $this->assertEquals('myCurrency', $payout->getCurrency());
         $this->assertEquals('https://my-return-url.test', $payout->getReturnUrl());
+        $this->assertEquals('my payment reference', $payout->getPaymentReference());
 
         $payout->setAmount(567.8)->setCurrency('myNewCurrency')->setReturnUrl('https://another-return-url.test');
+        $payout->setPaymentReference('different payment reference');
         $this->assertEquals(567.8, $payout->getAmount());
         $this->assertEquals('myNewCurrency', $payout->getCurrency());
         $this->assertEquals('https://another-return-url.test', $payout->getReturnUrl());
+        $this->assertEquals('different payment reference', $payout->getPaymentReference());
     }
 
     /**
diff --git a/test/unit/Services/HttpServiceTest.php b/test/unit/Services/HttpServiceTest.php
index c10749e1..cd012d15 100755
--- a/test/unit/Services/HttpServiceTest.php
+++ b/test/unit/Services/HttpServiceTest.php
@@ -280,7 +280,7 @@ public function handleErrorsShouldThrowExceptionIfResponseCodeIsGoE400($response
 
         $this->expectException(HeidelpayApiException::class);
         $this->expectExceptionMessage('The payment api returned an error!');
-        $this->expectExceptionCode('');
+        $this->expectExceptionCode('No error code provided');
 
         /** @var HttpService $httpServiceMock*/
         $httpServiceMock->send('/my/uri/123', $resource);
@@ -297,7 +297,6 @@ public function handleErrorsShouldThrowExceptionIfResponseCodeIsGoE400($response
     public function handleErrorsShouldThrowExceptionIfResponseContainsErrorField()
     {
         $httpServiceMock = $this->getMockBuilder(HttpService::class)->setMethods(['getAdapter'])->getMock();
-
         $adapterMock = $this->getMockBuilder(CurlAdapter::class)->setMethods(
             ['init', 'setUserAgent', 'setHeaders', 'execute', 'getResponseCode', 'close']
         )->getMock();
@@ -306,13 +305,10 @@ public function handleErrorsShouldThrowExceptionIfResponseContainsErrorField()
         $secondResponse = '{"errors": [{"merchantMessage": "This is an error message for the merchant!"}]}';
         $thirdResponse = '{"errors": [{"customerMessage": "This is an error message for the customer!"}]}';
         $fourthResponse = '{"errors": [{"code": "This is the error code!"}]}';
+        $fifthResponse = '{"errors": [{"code": "This is the error code!"}], "id": "s-err-1234"}';
+        $sixthResponse = '{"errors": [{"code": "This is the error code!"}], "id": "s-rre-1234"}';
 
-        $adapterMock->method('execute')->willReturnOnConsecutiveCalls(
-            $firstResponse,
-            $secondResponse,
-            $thirdResponse,
-            $fourthResponse
-        );
+        $adapterMock->method('execute')->willReturnOnConsecutiveCalls($firstResponse, $secondResponse, $thirdResponse, $fourthResponse, $fifthResponse, $sixthResponse);
         $httpServiceMock->method('getAdapter')->willReturn($adapterMock);
 
         $resource  = (new DummyResource())->setParentResource(new Heidelpay('s-priv-MyTestKey'));
@@ -324,7 +320,8 @@ public function handleErrorsShouldThrowExceptionIfResponseContainsErrorField()
         } catch (HeidelpayApiException $e) {
             $this->assertEquals('The payment api returned an error!', $e->getMerchantMessage());
             $this->assertEquals('The payment api returned an error!', $e->getClientMessage());
-            $this->assertEmpty($e->getCode());
+            $this->assertEquals('No error code provided', $e->getCode());
+            $this->assertEquals('No error id provided', $e->getErrorId());
         }
 
         try {
@@ -333,7 +330,8 @@ public function handleErrorsShouldThrowExceptionIfResponseContainsErrorField()
         } catch (HeidelpayApiException $e) {
             $this->assertEquals('This is an error message for the merchant!', $e->getMerchantMessage());
             $this->assertEquals('The payment api returned an error!', $e->getClientMessage());
-            $this->assertEmpty($e->getCode());
+            $this->assertEquals('No error code provided', $e->getCode());
+            $this->assertEquals('No error id provided', $e->getErrorId());
         }
 
         try {
@@ -342,7 +340,8 @@ public function handleErrorsShouldThrowExceptionIfResponseContainsErrorField()
         } catch (HeidelpayApiException $e) {
             $this->assertEquals('The payment api returned an error!', $e->getMerchantMessage());
             $this->assertEquals('This is an error message for the customer!', $e->getClientMessage());
-            $this->assertEmpty($e->getCode());
+            $this->assertEquals('No error code provided', $e->getCode());
+            $this->assertEquals('No error id provided', $e->getErrorId());
         }
 
         try {
@@ -352,6 +351,27 @@ public function handleErrorsShouldThrowExceptionIfResponseContainsErrorField()
             $this->assertEquals('The payment api returned an error!', $e->getMerchantMessage());
             $this->assertEquals('The payment api returned an error!', $e->getClientMessage());
             $this->assertEquals('This is the error code!', $e->getCode());
+            $this->assertEquals('No error id provided', $e->getErrorId());
+        }
+
+        try {
+            $httpServiceMock->send('/my/uri/123', $resource);
+            $this->assertTrue(false, 'The fifth exception should have been thrown!');
+        } catch (HeidelpayApiException $e) {
+            $this->assertEquals('The payment api returned an error!', $e->getMerchantMessage());
+            $this->assertEquals('The payment api returned an error!', $e->getClientMessage());
+            $this->assertEquals('This is the error code!', $e->getCode());
+            $this->assertEquals('s-err-1234', $e->getErrorId());
+        }
+
+        try {
+            $httpServiceMock->send('/my/uri/123', $resource);
+            $this->assertTrue(false, 'The sixth exception should have been thrown!');
+        } catch (HeidelpayApiException $e) {
+            $this->assertEquals('The payment api returned an error!', $e->getMerchantMessage());
+            $this->assertEquals('The payment api returned an error!', $e->getClientMessage());
+            $this->assertEquals('This is the error code!', $e->getCode());
+            $this->assertEquals('No error id provided', $e->getErrorId());
         }
     }
 
diff --git a/test/unit/Services/ResourceServiceTest.php b/test/unit/Services/ResourceServiceTest.php
index 6fcb4249..b646736f 100755
--- a/test/unit/Services/ResourceServiceTest.php
+++ b/test/unit/Services/ResourceServiceTest.php
@@ -407,7 +407,6 @@ public function fetchPaymentByOrderIdShouldCreatePaymentObjectWithOrderIdAndCall
                 static function ($payment) use ($heidelpay) {
                     return $payment instanceof Payment &&
                     $payment->getOrderId() === 'myOrderId' &&
-                    $payment->getId() === 'myOrderId' &&
                     $payment->getHeidelpayObject() === $heidelpay;
                 }));