class DeclinedPaymentException(DeclinedTransactionException):
    """
    Represents an error response from a create payment call.
    """

    def __init__(self, status_code, response_body, errors):
        if errors is not None:
            super(DeclinedPaymentException, self).__init__(status_code,
                                                           response_body,
                                                           errors.error_id,
                                                           errors.errors,
                                                           DeclinedPaymentException.__create_message(
                                                               errors))
        else:
            super(DeclinedPaymentException, self).__init__(status_code,
                                                           response_body,
                                                           None, None,
                                                           DeclinedPaymentException.__create_message())
        self.__errors = errors

    @staticmethod
    def __create_message(errors=None):
        if errors is not None and errors.payment_result is not None:
            payment = errors.payment_result.payment
        else:
            payment = None
        if payment is not None:
            return "declined payment '" + payment.id + "' with status '" + payment.status + "'"
        else:
            return "the Ingenico ePayments platform returned a declined payment response"

    @property
    def create_payment_result(self):
        """
        :return: The result of creating a payment if available, otherwise None.
        """
        if self.__errors is None:
            return None
        else:
            return self.__errors.payment_result class DefaultAuthenticator(Authenticator):
    """
    Default Authenticator implementation.
    """

    def __init__(self, authorization_type, api_id_key, secret_api_key):
        """
        :param authorization_type: Based on this value both the Ingenico ePayments
         platform and the merchant know which security implementation is used.
         A version number is used for backward compatibility in the future.
        :param api_id_key: An identifier for the secret API key. The api_key_id
         can be retrieved from the Configuration Center. This identifier is
         visible in the HTTP request and is also used to identify the correct
         account.
        :param secret_api_key: A shared secret. The shared secret can be
         retrieved from the Configuration Center. An api_key_id and
         secret_api_key always go hand-in-hand, the difference is that
         secret_api_key is never visible in the HTTP request. This secret is
         used as input for the HMAC algorithm.
        """
        Authenticator.__init__(self)
        if authorization_type is None:
            raise ValueError("authorization_type is required")
        if secret_api_key is None or not secret_api_key.strip():
            raise ValueError("secret_api_key is required")
        if api_id_key is None or not api_id_key.strip():
            raise ValueError("api_id_key is required")
        self.__authorization_type = authorization_type
        self.__api_id_key = api_id_key
        self.__secret_api_key = secret_api_key

    def create_simple_authentication_signature(self, http_method, resource_uri,
                                               http_headers):
        """Returns a v1HMAC authentication signature header"""
        if http_method is None or not http_method.strip():
            raise ValueError("http_method is required")
        if resource_uri is None:
            raise ValueError("resource_uri is required")
        data_to_sign = self.to_data_to_sign(http_method, resource_uri,
                                            http_headers)
        return "GCS " + self.__authorization_type + ":" + self.__api_id_key + \
               ":" + self.create_authentication_signature(data_to_sign)

    def to_data_to_sign(self, http_method, resource_uri, http_headers):
        content_type = None
        date = None
        canonicalized_headers = ""
        canonicalized_resource = self.__to_canonicalized_resource(resource_uri)
        xgcs_http_headers = []
        if http_headers is not None:
            for http_header in http_headers:
                if "Content-Type".lower() == http_header.name.lower():
                    content_type = http_header.value
                elif "Date".lower() == http_header.name.lower():
                    date = http_header.value
                else:
                    name = self.__to_canonicalize_header_name(http_header.name)
                    if name.startswith("x-gcs"):
                        value = self.to_canonicalize_header_value(
                            http_header.value)
                        xgcs_http_header = RequestHeader(name, value)
                        xgcs_http_headers.append(xgcs_http_header)
        xgcs_http_headers.sort(key=attrgetter('name'))
        for xgcs_http_header in xgcs_http_headers:
            canonicalized_headers += xgcs_http_header.name + ":" + xgcs_http_header.value + "\n"
        string = http_method.upper() + "\n"
        if content_type is not None:
            string += content_type + "\n"
        else:
            string += "\n"
        string += date + "\n"
        string += str(canonicalized_headers)
        string += canonicalized_resource + "\n"
        return str(string)

    def __to_canonicalized_resource(self, resource_uri):
        """
        Returns the encoded URI path without the HTTP method and including all
        decoded query parameters.
        """
        string = ""
        string += resource_uri.path
        if resource_uri.query:
            string += "?" + resource_uri.query
        return str(string)

    def __to_canonicalize_header_name(self, original_name):
        if original_name is None:
            return None
        else:
            return original_name.lower()

    def to_canonicalize_header_value(self, original_value):
        # For now V1HMAC is the only supported AuthorizationType so always use
        # the same logic.
        if original_value is None:
            return ""
        return sub(r"\r?\n(?:(?![\r\n])\s)*", " ", original_value).strip()

    def create_authentication_signature(self, data_to_sign):
        sig = hmac.new(self.__secret_api_key.encode("utf-8"), data_to_sign.encode("utf-8"),
                       hashlib.sha256)
        return b64encode(sig.digest()).decode("utf-8").rstrip('\n') class AmountOfMoney(DataObject):

    __amount = None
    __currency_code = None

    @property
    def amount(self):
        """
        | Amount in cents and always having 2 decimals
        
        Type: int
        """
        return self.__amount

    @amount.setter
    def amount(self, value):
        self.__amount = value

    @property
    def currency_code(self):
        """
        | Three-letter ISO currency code representing the currency for the amount
        
        Type: str
        """
        return self.__currency_code

    @currency_code.setter
    def currency_code(self, value):
        self.__currency_code = value

    def to_dictionary(self):
        dictionary = super(AmountOfMoney, self).to_dictionary()
        if self.amount is not None:
            dictionary['amount'] = self.amount
        if self.currency_code is not None:
            dictionary['currencyCode'] = self.currency_code
        return dictionary

    def from_dictionary(self, dictionary):
        super(AmountOfMoney, self).from_dictionary(dictionary)
        if 'amount' in dictionary:
            self.amount = dictionary['amount']
        if 'currencyCode' in dictionary:
            self.currency_code = dictionary['currencyCode']
        return self class Factory:
    """
    Ingenico ePayments platform factory for several SDK components.
    """

    def __init__(self):
        pass

    @staticmethod
    def create_configuration(configuration_file_name, api_key_id,
                             secret_api_key):
        """
        Creates a CommunicatorConfiguration based on the configuration
        values in configuration_file_name, api_key_id and secret_api_key.
        """
        try:
            parser = ConfigParser()
            parser.read(configuration_file_name)
            with open(configuration_file_name) as f:
                parser.read_file(f)
            return CommunicatorConfiguration(properties=parser,
                                             api_key_id=api_key_id,
                                             secret_api_key=secret_api_key)
        except IOError as e:
            raise RuntimeError("Unable to read configuration located at {}".format(e.filename), e)

    @staticmethod
    def create_session_from_configuration(communicator_configuration,
                                          meta_data_provider=None,
                                          connection=None, authenticator=None):
        """
        Creates a Session based on the configuration stored in the
        CommunicatorConfiguration argument
        """
        # If an authenticator is not given,  api_key_id and secret_api_key are
        # used to create a DefaultAuthenticator used to create the session.
        if meta_data_provider is None:
            meta_data_provider = MetaDataProvider(
                integrator=communicator_configuration.integrator,
                shopping_cart_extension=
                communicator_configuration.shopping_cart_extension)
        if connection is None:
            connection = DefaultConnection(
                communicator_configuration.connect_timeout,
                communicator_configuration.socket_timeout,
                communicator_configuration.max_connections,
                communicator_configuration.proxy_configuration)
        if authenticator is None:
            authenticator = DefaultAuthenticator(
                communicator_configuration.authorization_type,
                communicator_configuration.api_key_id,
                communicator_configuration.secret_api_key)
        return Session(api_endpoint=communicator_configuration.api_endpoint,
                       meta_data_provider=meta_data_provider,
                       connection=connection, authenticator=authenticator)

    @staticmethod
    def create_session_from_file(configuration_file_name, api_key_id,
                                 secret_api_key,
                                 meta_data_provider=None,
                                 connection=None, authenticator=None):
        """
        Creates a Session based on the configuration values in
        configuration_file_name, api_id_key and secret_api_key.
        """
        configuration = Factory.create_configuration(configuration_file_name,
                                                     api_key_id,
                                                     secret_api_key)
        return Factory.create_session_from_configuration(
            configuration,
            meta_data_provider=meta_data_provider,
            connection=connection,
            authenticator=authenticator)

    @staticmethod
    def create_communicator_from_session(session):
        """
        Create a Communicator based on the settings stored in the Session
        argument
        """
        return Communicator(session, DefaultMarshaller.INSTANCE())

    @staticmethod
    def create_communicator_from_configuration(communicator_configuration):
        """
        Create a Communicator based on the configuration stored in the
        CommunicatorConfiguration argument
        """
        session = Factory.create_session_from_configuration(
            communicator_configuration)
        return Communicator(session, DefaultMarshaller.INSTANCE())

    @staticmethod
    def create_communicator_from_file(configuration_file_name, api_key_id,
                                      secret_api_key):
        """
        Creates a Communicator based on the configuration values in
        configuration_file_name, api_key_id and secret_api_key.
        """
        configuration = Factory.create_configuration(configuration_file_name,
                                                     api_key_id, secret_api_key)
        session = Factory.create_session_from_configuration(configuration)
        return Communicator(session, DefaultMarshaller.INSTANCE())

    @staticmethod
    def create_client_from_configuration(communicator_configuration):
        """
        Create a Client based on the configuration stored in the
        CommunicatorConfiguration argument
        """
        communicator = Factory.create_communicator_from_configuration(
            communicator_configuration)
        return Client(communicator)

    @staticmethod
    def create_client_from_communicator(communicator):
        """
        Create a Client based on the settings stored in the Communicator
        argument
        """
        return Client(communicator)

    @staticmethod
    def create_client_from_session(session):
        """
        Create a Client based on the settings stored in the Session argument
        """
        communicator = Factory.create_communicator_from_session(session)
        return Client(communicator)

    @staticmethod
    def create_client_from_file(configuration_file_name, api_key_id,
                                secret_api_key):
        """
        Creates a Client based on the configuration values in
        configuration_file_name, api_key_id and secret_api_key.
        """
        communicator = Factory.create_communicator_from_file(
            configuration_file_name, api_key_id, secret_api_key)
        return Client(communicator) class IdempotenceException(ApiException):
    """
    Represents an error response from the Ingenico ePayments platform when an
    idempotent request failed because the first request has not finished yet.
    """

    def __init__(self, idempotence_key, idempotence_request_timestamp,
                 status_code, response_body, error_id, errors,
                 message="the Ingenico ePayments platform returned a duplicate request error response"):
        super(IdempotenceException, self).__init__(status_code, response_body,
                                                   error_id, errors, message)
        self.__idempotence_key = idempotence_key
        self.__idempotence_request_timestamp = idempotence_request_timestamp

    @property
    def idempotence_key(self):
        """
        :return: The key that was used for the idempotent request.
        """
        return self.__idempotence_key

    @property
    def idempotence_request_timestamp(self):
        """
        :return: The request timestamp of the first idempotent request with the
         same key.
        """
        return self.__idempotence_request_timestamp class MetaDataProvider:
    """
    Provides meta info about the server.
    """
    __SDK_VERSION = "3.30.0"
    __SERVER_META_INFO_HEADER = "X-GCS-ServerMetaInfo"
    __prohibited_headers = [__SERVER_META_INFO_HEADER, "X-GCS-Idempotence-Key",
                            "Date", "Content-Type", "Authorization"]
    __PROHIBITED_HEADERS = tuple(sorted(__prohibited_headers, key=str.lower))
    __meta_data_headers = None

    class ServerMetaInfo(DataObject):
        platform_identifier = None
        sdk_identifier = None
        sdk_creator = None
        integrator = None
        shopping_cart_extension = None

        def to_dictionary(self):
            dictionary = super(MetaDataProvider.ServerMetaInfo, self).to_dictionary()
            if self.platform_identifier is not None:
                dictionary['platformIdentifier'] = self.platform_identifier
            if self.sdk_identifier is not None:
                dictionary['sdkIdentifier'] = self.sdk_identifier
            if self.sdk_creator is not None:
                dictionary['sdkCreator'] = self.sdk_creator
            if self.integrator is not None:
                dictionary['integrator'] = self.integrator
            if self.shopping_cart_extension is not None:
                dictionary['shoppingCartExtension'] = self.shopping_cart_extension.to_dictionary()
            return dictionary

        def from_dictionary(self, dictionary):
            super(MetaDataProvider.ServerMetaInfo, self).from_dictionary(dictionary)
            if 'platformIdentifier' in dictionary:
                self.platform_identifier = dictionary['platformIdentifier']
            if 'sdkIdentifier' in dictionary:
                self.sdk_identifier = dictionary['sdkIdentifier']
            if 'sdkCreator' in dictionary:
                self.sdk_creator = dictionary['sdkCreator']
            if 'integrator' in dictionary:
                self.integrator = dictionary['integrator']
            if 'shoppingCartExtension' in dictionary:
                if not isinstance(dictionary['shoppingCartExtension'], dict):
                    raise TypeError('value \'{}\' is not a dictionary'.format(dictionary['shoppingCartExtension']))
                self.shopping_cart_extension = ShoppingCartExtension.create_from_dictionary(dictionary['shoppingCartExtension'])
            return self

    def __init__(self, integrator, shopping_cart_extension=None,
                 additional_request_headers=()):
        MetaDataProvider.__validate_additional_request_headers(
            additional_request_headers)

        def subber(name_or_value):
            return re.sub(r'\r?\n(?:(?![\r\n])\s)*', " ", name_or_value).strip()
        additional_request_headers = [RequestHeader(subber(header.name), subber(header.value))
                                      for header in additional_request_headers]

        server_meta_info = self.ServerMetaInfo()
        server_meta_info.platform_identifier = self._platform_identifier
        server_meta_info.sdk_identifier = self._sdk_identifier
        server_meta_info.sdk_creator = "Ingenico"
        server_meta_info.integrator = integrator
        server_meta_info.shopping_cart_extension = shopping_cart_extension

        server_meta_info_string = DefaultMarshaller.INSTANCE().marshal(
            server_meta_info)
        server_meta_info_header = RequestHeader(
            self.__SERVER_META_INFO_HEADER, b64encode(
                server_meta_info_string.encode('utf-8')))
        if not additional_request_headers:
            self.__meta_data_headers = tuple([server_meta_info_header])
        else:
            request_headers = [server_meta_info_header]
            request_headers.extend(additional_request_headers)
            self.__meta_data_headers = tuple(request_headers)

    @staticmethod
    def __validate_additional_request_headers(additional_request_headers):
        if additional_request_headers is not None:
            for additional_request_header in additional_request_headers:
                MetaDataProvider.__validate_additional_request_header(
                    additional_request_header)

    @staticmethod
    def __validate_additional_request_header(additional_request_header):
        try:
            if additional_request_header.name in MetaDataProvider.__PROHIBITED_HEADERS:
                raise ValueError("request header not allowed: ",
                                 str(additional_request_header))
        except AttributeError:
            raise AttributeError("Each request header should have an attribute 'name' and an attribute 'value'")

    @IterProperty
    def prohibited_headers(self):
        return self.__PROHIBITED_HEADERS

    @property
    def meta_data_headers(self):
        """
        :return: The server related headers containing the META data to be
         associated with the request (if any). This will always contain at least
         an automatically generated header X-GCS-ServerMetaInfo.
        """
        return self.__meta_data_headers

    @property
    def _platform_identifier(self):
        return platform.system() + " " + platform.release() + "/" + \
               platform.version() + " Python/" + platform.python_version() + \
               " (" + platform.python_implementation() + "; " + \
               str(platform.python_compiler()) + ")"

    @property
    def _sdk_identifier(self):
        return "Python3ServerSDK/v" + self.__SDK_VERSION class ReferenceException(ApiException):
    """
    Represents an error response from the Ingenico ePayments platform when a
    non-existing or removed object is trying to be accessed.
    """

    def __init__(self, status_code, response_body, error_id, errors,
                 message="the Ingenico ePayments platform returned a "
                         "reference error response"):
        super(ReferenceException, self).__init__(status_code, response_body,
                                                 error_id, errors, message) class ResponseException(RuntimeError):
    """
    Thrown when a response was received from the Ingenico ePayments platform which
    indicates an error.
    """

    def __init__(self, status, body, headers):
        super(ResponseException, self).__init__(
            "the Ingenico ePayments platform returned an error response")
        self.__status_code = status
        self.__headers = headers if headers is not None else {}
        self.__body = body

    @property
    def status_code(self):
        """
        :return: The HTTP status code that was returned by the Ingenico ePayments
         platform.
        """
        return self.__status_code

    @property
    def body(self):
        """
        :return: The raw response body that was returned by the Ingenico ePayments
         platform.
        """
        return self.__body

    @property
    def headers(self):
        """
        :return: The headers that were returned by the Ingenico ePayments platform.
         Never None.
        """
        return self.__headers

    def get_header(self, header_name):
        """
        :return: The header with the given name, or None if there was no such
         header.
        """
        return get_header(self.__headers, header_name)

    def get_header_value(self, header_name):
        """
        :return: The value header with the given name, or None if there was no
         such header.
        """
        return get_header_value(self.__headers, header_name)

    def __str__(self):
        string = super(ResponseException, self).__str__()
        status_code = self.__status_code
        if status_code > 0:
            string += "; status_code=" + str(status_code)
        response_body = self.__body
        if response_body:
            string += "; response_body='" + response_body + "'"
        return str(string) class Session(object):
    """
    Contains the components needed to communicate with the Ingenico ePayments platform.

    :param api_endpoint: the address of the target server as a string or ParseResult object
    """
    def __init__(self, api_endpoint, connection, authenticator,
                 meta_data_provider):
        if api_endpoint is None:
            raise ValueError("api_endpoint is required")
        if isinstance(api_endpoint, str):
            api_endpoint = urlparse(api_endpoint)
        if not api_endpoint.scheme.lower() in ["http", "https"] or not api_endpoint.netloc:
            raise ValueError("invalid api_endpoint: " + api_endpoint)
        if api_endpoint.path:
            raise ValueError("api_endpoint should not contain a path")
        if api_endpoint.username is not None or api_endpoint.query or api_endpoint.fragment:
            raise ValueError(
                "api_endpoint should not contain user info, query or fragment")
        if connection is None:
            raise ValueError("connection is required")
        if authenticator is None:
            raise ValueError("authenticator is required")
        if meta_data_provider is None:
            raise ValueError("meta_data_provider is required")
        self.__api_endpoint = api_endpoint
        self.__connection = connection
        self.__authenticator = authenticator
        self.__meta_data_provider = meta_data_provider

    @property
    def api_endpoint(self):
        """
        :return: The Ingenico ePayments platform API endpoint URI. This URI's path
         will be None or empty.
        """
        return self.__api_endpoint

    @property
    def connection(self):
        """
        :return: The Connection object associated with this session. Never None.
        """
        return self.__connection

    @property
    def authenticator(self):
        """
        :return: The Authenticator object associated with this session. Never None.
        """
        return self.__authenticator

    @property
    def meta_data_provider(self):
        """
        :return: The MetaDataProvider object associated with this session. Never None.
        """
        return self.__meta_data_provider class ValidationException(ApiException):
    """
    Represents an error response from the Ingenico ePayments platform when validation
    of requests failed.
    """
    def __init__(self, status_code, response_body, error_id, errors,
                 message="the Ingenico ePayments platform returned an incorrect "
                         "request error response"):
        super(ValidationException, self).__init__(status_code, response_body,
                                                  error_id, errors, message)
import os
import unittest
from unittest.mock import MagicMock
from ingenico.connect.sdk.api_exception import ApiException
from ingenico.connect.sdk.call_context import CallContext
from ingenico.connect.sdk.communication_exception import CommunicationException
from ingenico.connect.sdk.connection import Connection
from ingenico.connect.sdk.declined_payment_exception import DeclinedPaymentException
from ingenico.connect.sdk.defaultimpl.authorization_type import AuthorizationType
from ingenico.connect.sdk.defaultimpl.default_authenticator import DefaultAuthenticator
from ingenico.connect.sdk.domain.definitions.address import Address
from ingenico.connect.sdk.domain.definitions.amount_of_money import AmountOfMoney
from ingenico.connect.sdk.domain.definitions.card import Card
from ingenico.connect.sdk.domain.payment.create_payment_request import CreatePaymentRequest
from ingenico.connect.sdk.domain.payment.definitions.card_payment_method_specific_input import \
    CardPaymentMethodSpecificInput
from ingenico.connect.sdk.domain.payment.definitions.customer import Customer
from ingenico.connect.sdk.domain.payment.definitions.order import Order
from ingenico.connect.sdk.factory import Factory
from ingenico.connect.sdk.idempotence_exception import IdempotenceException
from ingenico.connect.sdk.meta_data_provider import MetaDataProvider
from ingenico.connect.sdk.not_found_exception import NotFoundException
from ingenico.connect.sdk.reference_exception import ReferenceException
from ingenico.connect.sdk.response_exception import ResponseException
from ingenico.connect.sdk.session import Session
from ingenico.connect.sdk.validation_exception import ValidationException
from tests import file_utils

    def test_create_rejected(self):
        """Tests that a 400 failure response with a payment result will throw a DeclinedPaymentException"""
        client = Factory.create_client_from_session(self.session)
        response_body = read_resource("rejected.json")
        request_body = create_request()

        def receive_post(uri, request_headers, body):
            def generate_response():
                for start in range(0, len(response_body), 1024):
                    yield response_body[start: start + 1024].encode('utf-8')
            return 400, None, generate_response()
        self.mock_connection.post.side_effect = receive_post

        with self.assertRaises(DeclinedPaymentException) as context:
            client.merchant("merchantId").payments().create(request_body)
        self.assertIn("payment '000002000020142544360000100001'", str(context.exception.args))
        self.assertIn("status 'REJECTED'", str(context.exception))
        self.assertIn(response_body, str(context.exception))
        self.assertIsNotNone(context.exception.create_payment_result)
        self.assertEqual("000002000020142544360000100001", context.exception.create_payment_result.payment.id)
        self.assertEqual("REJECTED", context.exception.create_payment_result.payment.status)

    def test_create_invalid_request(self):
        """Tests that a 400 failure response without a payment result will throw a ValidationException"""
        client = Factory.create_client_from_session(self.session)
        response_body = read_resource("invalid_request.json")
        request_body = create_request()

        def receive_post(uri, request_headers, body):
            def generate_response():
                for start in range(0, len(response_body), 1024):
                    yield response_body[start: start + 1024].encode('utf-8')
            return 400, None, generate_response()
        self.mock_connection.post.side_effect = receive_post

        with self.assertRaises(ValidationException) as exception:
            client.merchant("merchantId").payments().create(request_body)
        self.assertIn(response_body, str(exception.exception))

    def test_create_invalid_authorization(self):
        """Tests that a 401 failure response without a payment result will throw an ApiException"""
        client = Factory.create_client_from_session(self.session)
        response_body = read_resource("invalid_authorization.json")
        request_body = create_request()

        def receive_post(uri, request_headers, body):
            def generate_response():
                for start in range(0, len(response_body), 1024):
                    yield response_body[start: start + 1024].encode('utf-8')
            return 401, None, generate_response()
        self.mock_connection.post.side_effect = receive_post

        with self.assertRaises(ApiException) as exception:
            client.merchant("merchantId").payments().create(request_body)
        self.assertIn(response_body, str(exception.exception))

    def test_create_reference_error(self):
        """Tests that a 409 failure response with a duplicate request code
        but without an idempotence key will throw a ReferenceException
        """
        client = Factory.create_client_from_session(self.session)
        response_body = read_resource("duplicate_request.json")
        request_body = create_request()

        def receive_post(uri, request_headers, body):
            def generate_response():
                for start in range(0, len(response_body), 1024):
                    yield response_body[start: start + 1024].encode('utf-8')
            return 409, None, generate_response()
        self.mock_connection.post.side_effect = receive_post

        with self.assertRaises(ReferenceException) as exception:
            client.merchant("merchantId").payments().create(request_body)
        self.assertIn(response_body, str(exception.exception))

    def test_create_idempotence_error(self):
        """Tests that a 409 failure response with a duplicate request code
        and an idempotence key will throw an IdempotenceException
        """
        client = Factory.create_client_from_session(self.session)
        response_body = read_resource("duplicate_request.json")
        request_body = create_request()
        context = CallContext("key")

        def receive_post(uri, request_headers, body):
            def generate_response():
                for start in range(0, len(response_body), 1024):
                    yield response_body[start: start + 1024].encode('utf-8')
            return 409, None, generate_response()
        self.mock_connection.post.side_effect = receive_post

        with self.assertRaises(IdempotenceException) as exception:
            client.merchant("merchantId").payments().create(request_body, context)
        self.assertIn(response_body, str(exception.exception))
        self.assertEqual(context.idempotence_key, exception.exception.idempotence_key)

    def test_create_not_found(self):
        """Tests that a 404 response with a non-JSON response will throw an NotFoundException
        """
        client = Factory.create_client_from_session(self.session)
        response_body = read_resource("not_found.html")
        request_body = create_request()

        def receive_post(uri, request_headers, body):
            def generate_response():
                for start in range(0, len(response_body), 1024):
                    yield response_body[start: start + 1024].encode('utf-8')
            return 404, {"content-type": "text/html"}, generate_response()
        self.mock_connection.post.side_effect = receive_post

        with self.assertRaises(NotFoundException) as exception:
            client.merchant("merchantId").payments().create(request_body)
            self.assertIsNotNone(exception.exception.cause)
        self.assertIsInstance(exception.exception.cause, ResponseException)
        self.assertIn(response_body, str(exception.exception.cause))

    def test_create_method_not_allowed(self):
        """Tests that a 405 response with a non-JSON response will throw a CommunicationException
        """

        client = Factory.create_client_from_session(self.session)