Skip to content

Commit

Permalink
Fix the signature string with a new customer data sent to oneclick an…
Browse files Browse the repository at this point in the history
…d payment method
  • Loading branch information
HonzaSaibic committed Jul 23, 2024
1 parent 21bb597 commit d0a2e9a
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 3 deletions.
4 changes: 2 additions & 2 deletions pycsob/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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')
Expand Down
25 changes: 25 additions & 0 deletions pycsob/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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)))
163 changes: 162 additions & 1 deletion tests_pycsob/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -343,3 +343,164 @@ def test_convert_keys_to_camel_case_should_log_error_for_not_string_keys(self):
"Incorrect value type '<class 'int'>' 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

0 comments on commit d0a2e9a

Please sign in to comment.