Exemple #1
0
    def sign_message(self, msg, private_certificate, private_key, password):
        """
        Return the signed message.

        :param msg: The unsigned XML message to sign.
        :param private_certificate: File path to the Merchant's certificate file.
        :param private_key: File path to the Merchant's private key file.
        :param password: Password to unlock the ``private_key``.

        :return: The signed message.
        """

        signed_info = render_to_string(
            'templates/signed_info.xml',
            {'digest_value': str(self.get_message_digest(msg), 'utf-8')})

        signature = render_to_string(
            'templates/signature.xml', {
                'signed_info':
                signed_info,
                'signature_value':
                str(self.get_signature(signed_info, private_key, password),
                    'utf-8'),
                'key_name':
                str(self.get_fingerprint(private_certificate), 'utf-8'),
            })

        content, container_end = msg.rsplit('<', 1)

        return ''.join([content, signature, '<', container_end])
Exemple #2
0
    def get_transaction_status(self, transaction_id):
        """
        Sends an "AcquirerStatus" request to iDEAL to retrieve the status of given transaction.

        Eventually, the bank should redirect to your ``settings.MERCHANT_RETURN_URL`` (or the overriden value in
        ``IdealClient.start_transaction``). There are at least 2 query string parameters present in this URL: ``trxid``
        and ``ec``.

        :param transaction_id: The value of ``trxid`` query string parameter.

        :return: A :class:`TransactionResponse` object.
        """

        # Get all required variables for the template.
        context = self._get_context()
        context.update({
            'transaction_id': transaction_id,
        })

        data = render_to_string('templates/transaction_status_request.xml',
                                context)

        r = self._request(data)

        return StatusResponse(r)
Exemple #3
0
    def test_sign_message(self):
        """
        Test signing a message.
        """
        unsigned_msg_body, unsigned_msg_closing = self.unsigned_message.rsplit(
            '<', 1)
        unsigned_msg_closing = '<' + unsigned_msg_closing
        message_digest = str(
            self.security.get_message_digest(self.unsigned_message), 'utf-8')
        signed_info = render_to_string('templates/signed_info.xml',
                                       {'digest_value': message_digest})
        signature = self.security.get_signature(signed_info,
                                                self.priv_filepath, 'example')
        fingerprint = self.security.get_fingerprint(self.cert_filepath)

        expected_signed_message = """{msg_body}<Signature xmlns='http://www.w3.org/2000/09/xmldsig#'>
{signed_info}
 <SignatureValue>{signature_value}</SignatureValue>
 <KeyInfo>
   <KeyName>{key_name}</KeyName>
  </KeyInfo>
</Signature>{msg_closing}""".format(msg_body=unsigned_msg_body,
                                    signed_info=signed_info,
                                    signature_value=str(signature, 'utf-8'),
                                    key_name=str(fingerprint, 'utf-8'),
                                    msg_closing=unsigned_msg_closing)

        signed_message = self.security.sign_message(self.unsigned_message,
                                                    self.cert_filepath,
                                                    self.priv_filepath,
                                                    'example')

        self.assertEqual(expected_signed_message, signed_message)
Exemple #4
0
    def get_issuers(self):
        """
        Sends a "DirectoryReq" to iDEAL to retrieve a list of issuers (banks).

        NOTE: The iDEAL documentation indicates you should get a list of issuers (ie. call this function) every time
        you want to show a list of issuers. Cache these issuers locally; they update rarily.

        :return: A :class: `DirectoryResponse` object.
        """
        context = self._get_context()
        data = render_to_string('templates/directory_request.xml', context)

        r = self._request(data)

        return DirectoryResponse(r)
Exemple #5
0
    def test_get_signature(self):
        """
        Test the XML message SignatureValue.
        """
        message_digest = self.security.get_message_digest(
            self.unsigned_message)
        signed_info = render_to_string(
            'templates/signed_info.xml',
            {'digest_value': str(message_digest, 'utf-8')})

        expected_signature = b"""hmsonk9o7QMUlrVyewEC7+u76I7dy4S4aIuno9/Sj2J7Okfv0XUsGd2Sw7YU7zRy3yKdpHhbtMFtQhEsqm/eFBnzd1M+JpdUpAW60vBfa/lQ/RnwX6mBjl3r2vxhUVd3T8BFnnmh5qQ74AjvCYZ6eFXsrq4w6b1v+IQZHknC7qQeWX56VDuTv6ezZ4nnAIr2jL//xv3iaOsYRrSK0jRVU6cJyXqhkKEvIQHKFkOnJZt7BfQbMQ5goqbqdL3UI+U98bj/1/PTVDxYBvyK26YltX6X3tNB1ovI61BdxMXD/P35mvIq2fUJ3IeL0CGw1Epo34na9VtO7+tyIzedfysjUg=="""

        signature = self.security.get_signature(signed_info,
                                                self.priv_filepath, 'example')

        self.assertEqual(expected_signature, signature)
Exemple #6
0
    def start_transaction(self,
                          issuer_id,
                          purchase_id,
                          amount,
                          description,
                          entrance_code=None,
                          merchant_return_url=None,
                          expiration_period=None,
                          language=None):
        """
        Send an "AcquirerTrxReq" to iDEAL, starting the payment process.

        The returned named tuple can be used to store the transaction details but should always be used to redirect the
        customer to their bank's website by using the ``issuer_authentication_url``. The generated ``entrance_code`` can
        be used to resume an incomplete transaction process.

        :param issuer_id: The BIC code of the customer's bank. Usually retrieved with ``IdealClient.get_issuers``.
        :param purchase_id: Any string with a maximum of 16 characters to identify the purchase.
        :param amount: Decimal number for the amount of the transaction.
        :param description: Any string with a maximum of 32 characters describing the purchase.
        :param entrance_code: A unique string of 40 characters to continue the payment process (optional).
        :param merchant_return_url: Override the callback URL (optional). Default\: ``settings.MERCHANT_RETURN_URL``.
        :param expiration_period: Override the expiration period (optional). Default: ``settings.EXPIRATION_PERIOD``.
        :param language: Override the language (optional). Default\: ``settings.LANGUAGE``.

        :return: A :class:`TransactionResponse` object.
        """
        if merchant_return_url is None:
            merchant_return_url = settings.MERCHANT_RETURN_URL
        if language is None:
            language = settings.LANGUAGE
        if entrance_code is None:
            entrance_code = hashlib.sha1(
                uuid.uuid4().hex.encode('utf-8')).hexdigest()
        if expiration_period is None:
            expiration_period = settings.EXPIRATION_PERIOD

        try:
            if str(int(expiration_period)) == str(expiration_period):
                expiration_period = 'PT{n}M'.format(expiration_period)
        except ValueError:
            pass

        # Get all required variables for the template.
        context = self._get_context()
        context.update({
            'issuer_id': issuer_id,
            'merchant_return_url': merchant_return_url,
            'purchase_id': purchase_id[0:16],
            'amount': '{amount:.2f}'.format(
                amount=amount),  # Use period as decimal separator.
            'currency': 'EUR',  # ISO 4217, Only 'EUR' supported.
            'expiration_period':
            expiration_period,  # ISO 8601, min. is 1 minute, max. is 1 hour (optional).
            'language':
            language,  # ISO 639-1, only Dutch (nl) and English (en) are supported.
            'description': description[0:32],
            'entrance_code': entrance_code,
        })

        data = render_to_string('templates/transaction_request.xml', context)

        r = self._request(data)

        response = TransactionResponse(r)

        # Not an actual part of the response, but can be generated in this function and made conveniently accessible.
        response.entrance_code = entrance_code

        return response