From d0a2e9a7bd20b83272efc5e7842890389e6ceba1 Mon Sep 17 00:00:00 2001 From: Jan Saibic Date: Tue, 23 Jul 2024 16:47:42 +0200 Subject: [PATCH] Fix the signature string with a new customer data sent to oneclick and payment method --- pycsob/client.py | 4 +- pycsob/utils.py | 25 ++++++ tests_pycsob/test_api.py | 163 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 189 insertions(+), 3 deletions(-) diff --git a/pycsob/client.py b/pycsob/client.py index 85a65cf..e532e18 100644 --- a/pycsob/client.py +++ b/pycsob/client.py @@ -124,11 +124,11 @@ def payment_init(self, order_no, total_amount, return_url, description, customer ('payMethod', 'card'), ('totalAmount', total_amount), ('currency', currency), - ('customer', utils.convert_keys_to_camel_case(customer_data)), ('closePayment', close_payment), ('returnUrl', return_url), ('returnMethod', return_method), ('cart', cart), + ('customer', utils.convert_keys_to_camel_case(customer_data)), ('merchantData', merchant_data), ('customerId', customer_id), ('language', language), @@ -231,13 +231,13 @@ def oneclick_init(self, orig_pay_id, order_no, total_amount, customer_data, curr ('merchantId', self.merchant_id), ('origPayId', orig_pay_id), ('orderNo', str(order_no)), - ('customer', utils.convert_keys_to_camel_case(customer_data)), ('dttm', utils.dttm()), ('totalAmount', total_amount), ('currency', currency), ('description', description), ('returnUrl', return_url), ('returnMethod', return_method), + ('customer', utils.convert_keys_to_camel_case(customer_data)), ('clientInitiated', client_initiated), )) url = utils.mk_url(base_url=self.base_url, endpoint_url='oneclick/init') diff --git a/pycsob/utils.py b/pycsob/utils.py index 961e205..ad49d48 100644 --- a/pycsob/utils.py +++ b/pycsob/utils.py @@ -46,6 +46,8 @@ def mk_msg_for_sign(payload): for one in payload['cart']: cart_msg.extend(one.values()) payload['cart'] = '|'.join(map(str_or_jsbool, cart_msg)) + if 'customer' in payload and payload['customer'] not in conf.EMPTY_VALUES: + payload['customer'] = get_customer_data_signature_message(payload['customer']) msg = '|'.join(map(str_or_jsbool, payload.values())) return msg.encode('utf-8') @@ -165,3 +167,26 @@ def convert_keys_to_camel_case(data: T) -> T: else: converted_dict[key] = value return converted_dict + + +def get_customer_data_signature_message(customer_data: dict[str, Any]) -> str: + """ + Returns signature string from customer data used to sign the request. + For more information follow the API documentation + https://github.com/csob/platebnibrana/wiki/Podpis-po%C5%BEadavku-a-ov%C4%9B%C5%99en%C3%AD-podpisu-odpov%C4%9Bdi + """ + customer = ["name", "email", "mobilePhone"] + account = ["createdAt", "changedAt"] + login = ["auth", "authAt"] + + customer_msg = account_msg = login_msg = "" + + customer_msg = "|".join(str_or_jsbool(customer_data[key]) for key in customer if key in customer_data.keys()) + + if account_data := customer_data.get("account", {}): + account_msg = "|".join(str_or_jsbool(account_data[key]) for key in account if key in account_data.keys()) + + if login_data := customer_data.get("login", {}): + login_msg = "|".join(str_or_jsbool(login_data[key]) for key in login if key in login_data.keys()) + + return "|".join(filter(None, (customer_msg, account_msg, login_msg))) diff --git a/tests_pycsob/test_api.py b/tests_pycsob/test_api.py index 73e25d4..e0c4a1c 100644 --- a/tests_pycsob/test_api.py +++ b/tests_pycsob/test_api.py @@ -8,7 +8,7 @@ from freezegun import freeze_time from unittest import TestCase from requests.exceptions import HTTPError, ConnectionError -from pycsob.utils import convert_keys_to_camel_case, to_camel_case +from pycsob.utils import convert_keys_to_camel_case, to_camel_case, get_customer_data_signature_message, mk_msg_for_sign from pycsob import conf, utils from pycsob.client import CsobClient @@ -343,3 +343,164 @@ def test_convert_keys_to_camel_case_should_log_error_for_not_string_keys(self): "Incorrect value type '' during conversion to camcel case. String expected." ) + def test_get_customer_data_signature_message_should_return_correct_string_format(self): + customer = { + "name":"Jan Novák", + "email":"jan.novak@example.com", + "mobilePhone":"+420.800300300", + "account": { + "createdAt":"2022-01-12T12:10:37+01:00", + "changedAt":"2022-01-15T15:10:12+01:00" + }, + "login": { + "auth":"account", + "authAt":"2022-01-25T13:10:03+01:00" + } + } + + assert get_customer_data_signature_message(customer) == ( + "Jan Novák|jan.novak@example.com|+420.800300300" + "|2022-01-12T12:10:37+01:00|2022-01-15T15:10:12+01:00" + "|account|2022-01-25T13:10:03+01:00" + ) + + def test_get_customer_data_signature_message_should_be_able_to_skip_missing_values(self): + # missing account + customer = { + "name":"Jan Novák", + "email":"jan.novak@example.com", + "mobilePhone":"+420.800300300", + "login": { + "auth":"account", + "authAt":"2022-01-25T13:10:03+01:00" + } + } + + assert get_customer_data_signature_message(customer) == ( + "Jan Novák|jan.novak@example.com|+420.800300300|account|2022-01-25T13:10:03+01:00" + ) + + # missing login + customer = { + "name":"Jan Novák", + "email":"jan.novak@example.com", + "mobilePhone":"+420.800300300", + "account": { + "createdAt":"2022-01-12T12:10:37+01:00", + "changedAt":"2022-01-15T15:10:12+01:00" + }, + } + assert get_customer_data_signature_message(customer) == ( + "Jan Novák|jan.novak@example.com|+420.800300300|2022-01-12T12:10:37+01:00|2022-01-15T15:10:12+01:00" + ) + + # missing customer email + customer = { + "name":"Jan Novák", + "mobilePhone":"+420.800300300", + } + assert get_customer_data_signature_message(customer) == ( + "Jan Novák|+420.800300300" + ) + + # missing customer personal data + customer = { + "account": { + "createdAt":"2022-01-12T12:10:37+01:00", + } + } + assert get_customer_data_signature_message(customer) == ( + "2022-01-12T12:10:37+01:00" + ) + + def test_get_customer_data_signature_message_should_return_ignore_invalid_keys(self): + customer = { + "name":"Jan Novák", + "email":"jan.novak@example.com", + "mobilePhone":"+420.800300300", + "nonsense": "Test", + "account": { + "createdAt":"2022-01-12T12:10:37+01:00", + "changedAt":"2022-01-15T15:10:12+01:00", + "nonsense": "Test", + }, + "login": { + "auth":"account", + "authAt":"2022-01-25T13:10:03+01:00", + "nonsense": "Test", + } + } + assert get_customer_data_signature_message(customer) == ( + "Jan Novák|jan.novak@example.com|+420.800300300" + "|2022-01-12T12:10:37+01:00|2022-01-15T15:10:12+01:00" + "|account|2022-01-25T13:10:03+01:00" + ) + + def test_get_customer_data_signature_message_should_keep_correct_value_order(self): + customer = { + "mobilePhone":"+420.800300300", + "email":"jan.novak@example.com", + "name":"Jan Novák", + "login": { + "authAt":"2022-01-25T13:10:03+01:00", + "auth":"account", + }, + "account": { + "changedAt":"2022-01-15T15:10:12+01:00", + "createdAt":"2022-01-12T12:10:37+01:00", + }, + } + + assert get_customer_data_signature_message(customer) == ( + "Jan Novák|jan.novak@example.com|+420.800300300" + "|2022-01-12T12:10:37+01:00|2022-01-15T15:10:12+01:00" + "|account|2022-01-25T13:10:03+01:00" + ) + + def test_mk_msg_for_sign_should_return_correctly_ordered_message(self): + payload = { + "merchantId":"M1MIPS0000", + "orderNo":"5547", + "dttm":"20220125131559", + "payOperation":"payment", + "payMethod":"card", + "totalAmount":123400, + "currency":"CZK", + "closePayment": True, + "returnUrl":"https://shop.example.com/return", + "returnMethod":"POST", + "cart":[ + { + "name": "Wireless headphones", + "quantity": 1, + "amount": 123400 + }, + { + "name": "Shipping", + "quantity": 1, + "amount": 0, + "description": "DPL" + } + ], + "customer": { + "name":"Jan Novák", + "email":"jan.novak@example.com", + "mobilePhone":"+420.800300300", + "account": { + "createdAt":"2022-01-12T12:10:37+01:00", + "changedAt":"2022-01-15T15:10:12+01:00" + }, + "login": { + "auth":"account", + "authAt":"2022-01-25T13:10:03+01:00" + } + }, + } + expected_message = ( + "M1MIPS0000|5547|20220125131559|payment|card|123400|CZK|true|https://shop.example.com/return|POST" + "|Wireless headphones|1|123400|Shipping|1|0|DPL" + "|Jan Novák|jan.novak@example.com|+420.800300300" + "|2022-01-12T12:10:37+01:00|2022-01-15T15:10:12+01:00" + "|account|2022-01-25T13:10:03+01:00" + ).encode() + assert mk_msg_for_sign(payload) == expected_message