Repository: Adyen/adyen-python-api-library

Issue:
Util generate_hpp_sig() value and hmac do not correspond
**Python version**: 2.7.16
**Library version**: 2.1.0 (and tested too with future 2.2.0)
**Description**
TEST ENVIRONMENT

Implemented webhook for Adyen notifications (https://docs.adyen.com/development-resources/notifications) and using Test Configuration form in Adyen backend.

Passed JSON received data from Adyen to generate_hpp_sig() function according to test here : https://github.com/Adyen/adyen-python-api-library/blob/develop/test/UtilTest.py#L13

The generated hmac does not correspond to the addtionnalData['hmacSignature'].


Even tested with future 2.2.0 function is_valid_hmac, returns False.



Comments:

Patch:
diff --git a/Adyen/util.py b/Adyen/util.py
index 580ff31..6156876 100644
--- a/Adyen/util.py
+++ b/Adyen/util.py
@@ -44,3 +44,53 @@ def is_valid_hmac(dict_object, hmac_key):
             merchant_sign = generate_hpp_sig(dict_object, hmac_key)
             merchant_sign_str = merchant_sign.decode("utf-8")
             return merchant_sign_str == expected_sign
+
+
+def generate_notification_sig(dict_object, hmac_key):
+    if 'issuerId' in dict_object:
+        if dict_object['issuerId'] == "":
+            del dict_object['issuerId']
+
+    if not isinstance(dict_object, dict):
+        raise ValueError("Must Provide dictionary object")
+
+    def escape_val(val):
+        if isinstance(val, int):
+            return val
+        return val.replace('\\', '\\\\').replace(':', '\\:')
+
+    hmac_key = binascii.a2b_hex(hmac_key)
+
+    request_dict = dict(dict_object)
+    request_dict['value'] = request_dict['amount']['value']
+    request_dict['currency'] = request_dict['amount']['currency']
+
+    element_orders = [
+        'pspReference',
+        'originalReference',
+        'merchantAccountCode',
+        'merchantReference',
+        'value',
+        'currency',
+        'eventCode',
+        'success',
+    ]
+
+    signing_string = ':'.join(
+        map(escape_val, map(str, (
+            request_dict.get(element, '') for element in element_orders))))
+
+    hm = hmac.new(hmac_key, signing_string.encode('utf-8'), hashlib.sha256)
+    return base64.b64encode(hm.digest())
+
+
+def is_valid_hmac_notification(dict_object, hmac_key):
+    if 'additionalData' in dict_object:
+        if dict_object['additionalData']['hmacSignature'] == "":
+            raise ValueError("Must Provide hmacSignature in additionalData")
+        else:
+            expected_sign = dict_object['additionalData']['hmacSignature']
+            del dict_object['additionalData']
+            merchant_sign = generate_notification_sig(dict_object, hmac_key)
+            merchant_sign_str = merchant_sign.decode("utf-8")
+            return merchant_sign_str == expected_sign


Test patch:
diff --git a/test/UtilTest.py b/test/UtilTest.py
index acaf101..9cd9b33 100644
--- a/test/UtilTest.py
+++ b/test/UtilTest.py
@@ -1,15 +1,19 @@
 import unittest
 
 import Adyen
-from Adyen import generate_hpp_sig
-from Adyen.util import is_valid_hmac
+from Adyen.util import (
+    generate_hpp_sig,
+    is_valid_hmac,
+    generate_notification_sig,
+    is_valid_hmac_notification,
+)
 
 
 class UtilTest(unittest.TestCase):
     ady = Adyen.Adyen()
     client = ady.client
 
-    def test_notification_request_item_hmac(self):
+    def test_hpp_request_item_hmac(self):
         request = {
             "pspReference": "pspReference",
             "originalReference": "originalReference",
@@ -31,3 +35,33 @@ class UtilTest(unittest.TestCase):
         request['additionalData'] = {'hmacSignature': hmac_calculation_str}
         hmac_validate = is_valid_hmac(request, key)
         self.assertTrue(hmac_validate)
+
+    def test_notification_request_item_hmac(self):
+        request = {
+            "pspReference": "7914073381342284",
+            "merchantReference": "TestPayment-1407325143704",
+            "merchantAccountCode": "TestMerchant",
+            "amount": {
+                "currency": "EUR",
+                "value": 1130
+            },
+            "eventCode": "AUTHORISATION",
+            "success": "true",
+            "eventDate": "2019-05-06T17:15:34.121+02:00",
+            "operations": [
+               "CANCEL",
+               "CAPTURE",
+               "REFUND"
+            ],
+            "paymentMethod": "visa",
+        }
+        key = "44782DEF547AAA06C910C43932B1EB0C" \
+              "71FC68D9D0C057550C48EC2ACF6BA056"
+        hmac_calculation = generate_notification_sig(request, key)
+        hmac_calculation_str = hmac_calculation.decode("utf-8")
+        expected_hmac = "coqCmt/IZ4E3CzPvMY8zTjQVL5hYJUiBRg8UU+iCWo0="
+        self.assertTrue(hmac_calculation_str != "")
+        self.assertEqual(hmac_calculation_str, expected_hmac)
+        request['additionalData'] = {'hmacSignature': hmac_calculation_str}
+        hmac_validate = is_valid_hmac_notification(request, key)
+        self.assertTrue(hmac_validate)