python - 在嵌套字典上用 Python 实现 PHP 方法 KSORT 和 HTTP_BUILD_QUERY
问题描述
我需要在 Python 中重新创建以下代码,以验证 API 响应标头中包含的签名。
<?php
// Profile Key (ServerKey)
$serverKey = "SRJNLKHG6K-HWMDRGW66J-LRWTGDGRNK"; // Example
// Request body include a signature post Form URL encoded field
// 'signature' (hexadecimal encoding for hmac of sorted post form fields)
$requestSignature = $_POST["signature"];
unset($_POST["signature"]);
$signature_fields = $_POST;
// Ignore empty values fields
$signature_fields = array_filter($signature_fields);
// Sort form fields
ksort($signature_fields);
// Generate URL-encoded query string of Post fields except signature field.
$query = http_build_query($signature_fields);
$signature = hash_hmac('sha256', $query, $serverKey);
if (hash_equals($signature,$requestSignature) === TRUE) {
// VALID Redirect
// Do your logic
}else{
// INVALID Redirect
// Log request
}
?>
我对 PHP 的了解为 0,过去 24 小时我一直在尝试自己重新创建它,但无济于事。
我坚持的部分是ksort和http_build_query方法。
我想了解的是这些方法的结果对于 NESTED 数组会是什么样子,例如:
{
"tran_ref": "TST2014900000688",
"cart_id": "Sample Payment",
"cart_description": "Sample Payment",
"cart_currency": "AED",
"cart_amount": "1",
"customer_details": {
"name": "John Smith",
"email": "jsmith@gmail.com",
"phone": "9711111111111",
"street1": "404, 11th st, void",
"city": "Dubai",
"state": "DU",
"country": "AE",
"ip": "127.0.0.1"
},
"payment_result": {
"response_status": "A",
"response_code": "831000",
"response_message": "Authorised",
"acquirer_message": "ACCEPT",
"acquirer_rrn": "014910159369",
"transaction_time": "2020-05-28T14:35:38+04:00"
},
"payment_info": {
"card_type": "Credit",
"card_scheme": "Visa",
"payment_description": "4111 11## #### 1111"
}
}
如果 python 中有现成的方法可以实现这一点,那将非常有帮助。
一如既往地感谢。
解决方案
针对您关于ksort()
and的问题http_build_query()
,以下是使用 PHP 7.3 和以下代码时的结果(基于您给定的代码/数据)。首先,请注意,当我ksort()
在您的示例数据上运行时,似乎ksort()
对数据中的初始键进行了排序,但内部键似乎没有进行排序。
为了也对内部键进行排序,我编写了一个名为的简单函数sort_again()
,用于对内部键进行排序。普通ksort()
与结果的sort_again()
结果在以下示例 PHP 代码下方。
<?php
$jsonData = '
{
"tran_ref": "TST2014900000688",
"cart_id": "Sample Payment",
"cart_description": "Sample Payment",
"cart_currency": "AED",
"cart_amount": "1",
"customer_details": {
"name": "John Smith",
"email": "jsmith@gmail.com",
"phone": "9711111111111",
"street1": "404, 11th st, void",
"city": "Dubai",
"state": "DU",
"country": "AE",
"ip": "127.0.0.1"
},
"payment_result": {
"response_status": "A",
"response_code": "831000",
"response_message": "Authorised",
"acquirer_message": "ACCEPT",
"acquirer_rrn": "014910159369",
"transaction_time": "2020-05-28T14:35:38+04:00"
},
"payment_info": {
"card_type": "Credit",
"card_scheme": "Visa",
"payment_description": "4111 11## #### 1111"
}
}
';
$jsonArray = json_decode($jsonData, $flags=JSON_OBJECT_AS_ARRAY);
ksort($jsonArray);
echo "ksort() results:\n";
print_r($jsonArray);
// ref: q16.py
function sort_again($arr) {
ksort($arr);
$result = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
$result[$key] = sort_again($value);
} else {
$result[$key] = $value;
}
}
return $result;
}
$result = sort_again($jsonArray);
echo "sort_again() results:\n";
print_r($result);
$querystring = http_build_query($result);
echo "http_build_query() results:\n";
echo $querystring;
输出:
ksort() results:
Array
(
[cart_amount] => 1
[cart_currency] => AED
[cart_description] => Sample Payment
[cart_id] => Sample Payment
[customer_details] => Array
(
[name] => John Smith
[email] => jsmith@gmail.com
[phone] => 9711111111111
[street1] => 404, 11th st, void
[city] => Dubai
[state] => DU
[country] => AE
[ip] => 127.0.0.1
)
[payment_info] => Array
(
[card_type] => Credit
[card_scheme] => Visa
[payment_description] => 4111 11## #### 1111
)
[payment_result] => Array
(
[response_status] => A
[response_code] => 831000
[response_message] => Authorised
[acquirer_message] => ACCEPT
[acquirer_rrn] => 014910159369
[transaction_time] => 2020-05-28T14:35:38+04:00
)
[tran_ref] => TST2014900000688
)
sort_again() results:
Array
(
[cart_amount] => 1
[cart_currency] => AED
[cart_description] => Sample Payment
[cart_id] => Sample Payment
[customer_details] => Array
(
[city] => Dubai
[country] => AE
[email] => jsmith@gmail.com
[ip] => 127.0.0.1
[name] => John Smith
[phone] => 9711111111111
[state] => DU
[street1] => 404, 11th st, void
)
[payment_info] => Array
(
[card_scheme] => Visa
[card_type] => Credit
[payment_description] => 4111 11## #### 1111
)
[payment_result] => Array
(
[acquirer_message] => ACCEPT
[acquirer_rrn] => 014910159369
[response_code] => 831000
[response_message] => Authorised
[response_status] => A
[transaction_time] => 2020-05-28T14:35:38+04:00
)
[tran_ref] => TST2014900000688
)
http_build_query() results: cart_amount=1&cart_currency=AED&cart_description=Sample+Payment&cart_id=Sample+Payment&customer_details%5Bcity%5D=Dubai&customer_details%5Bcountry%5D=AE&customer_details%5Bemail%5D=jsmith%40gmail.com&customer_details%5Bip%5D=127.0.0.1&customer_details%5Bname%5D=John+Smith&customer_details%5Bphone%5D=9711111111111&customer_details%5Bstate%5D=DU&customer_details%5Bstreet1%5D=404%2C+11th+st%2C+void&payment_info%5Bcard_scheme%5D=Visa&payment_info%5Bcard_type%5D=Credit&payment_info%5Bpayment_description%5D=4111+11%23%23+%23%23%23%23+1111&payment_result%5Bacquirer_message%5D=ACCEPT&payment_result%5Bacquirer_rrn%5D=014910159369&payment_result%5Bresponse_code%5D=831000&payment_result%5Bresponse_message%5D=Authorised&payment_result%5Bresponse_status%5D=A&payment_result%5Btransaction_time%5D=2020-05-28T14%3A35%3A38%2B04%3A00&tran_ref=TST2014900000688
(注意:由于代码格式问题,输出实际上在该部分后面有一个换行符http_build_query() results:
,但是当我试图在这篇文章中显示它时,我似乎无法让代码块正常工作。)
现在,对于问题的第二部分,如果您使用 Python 执行与 类似的操作,下面的代码示例显示了如何在 Python 上下文中ksort()
使用该函数来完成此操作。sort_again()
此外,据我所知,http_build_query()
Python 中的类似选项可能类似于urllib.parse.urlencode()
. 但是,我确实注意到,此方法生成的查询字符串与 PHPhttp_build_query()
函数略有不同。更多关于这个在这篇文章的结尾。
import json
import pprint
from collections import OrderedDict
from urllib.parse import urlencode
obj = {
"tran_ref": "TST2014900000688",
"cart_id": "Sample Payment",
"cart_description": "Sample Payment",
"cart_currency": "AED",
"cart_amount": "1",
"customer_details": {
"name": "John Smith",
"email": "jsmith@gmail.com",
"phone": "9711111111111",
"street1": "404, 11th st, void",
"city": "Dubai",
"state": "DU",
"country": "AE",
"ip": "127.0.0.1"
},
"payment_result": {
"response_status": "A",
"response_code": "831000",
"response_message": "Authorised",
"acquirer_message": "ACCEPT",
"acquirer_rrn": "014910159369",
"transaction_time": "2020-05-28T14:35:38+04:00"
},
"payment_info": {
"card_type": "Credit",
"card_scheme": "Visa",
"payment_description": "4111 11## #### 1111"
}
}
def sort_again(obj):
sorted_obj = sorted(obj)
# Note: if you are using Python 3.7 or higher, you should be able
# to use a plain dictionary here instead of using an OrderedDict()
# because the dict class can now remember insertion order.
# ref: https://docs.python.org/3/library/collections.html
# #ordereddict-objects
#d = OrderedDict()
d = {}
i = 0
for key in sorted_obj:
if isinstance(obj[key], dict):
d[sorted_obj[i]] = sort_again(obj[key])
else:
d[sorted_obj[i]] = obj[key]
i += 1
return d
results = sort_again(obj)
print("sort_again() results:")
pprint.pprint(results)
encoded = urlencode(results)
print("urlencode() results:")
print(encoded)
输出:
sort_again() results:
{'cart_amount': '1',
'cart_currency': 'AED',
'cart_description': 'Sample Payment',
'cart_id': 'Sample Payment',
'customer_details': {'city': 'Dubai',
'country': 'AE',
'email': 'jsmith@gmail.com',
'ip': '127.0.0.1',
'name': 'John Smith',
'phone': '9711111111111',
'state': 'DU',
'street1': '404, 11th st, void'},
'payment_info': {'card_scheme': 'Visa',
'card_type': 'Credit',
'payment_description': '4111 11## #### 1111'},
'payment_result': {'acquirer_message': 'ACCEPT',
'acquirer_rrn': '014910159369',
'response_code': '831000',
'response_message': 'Authorised',
'response_status': 'A',
'transaction_time': '2020-05-28T14:35:38+04:00'},
'tran_ref': 'TST2014900000688'}
urlencode() results:
cart_amount=1&cart_currency=AED&cart_description=Sample+Payment&cart_id=Sample+Payment&customer_details=%7B%27city%27%3A+%27Dubai%27%2C+%27country%27%3A+%27AE%27%2C+%27email%27%3A+%27jsmith%40gmail.com%27%2C+%27ip%27%3A+%27127.0.0.1%27%2C+%27name%27%3A+%27John+Smith%27%2C+%27phone%27%3A+%279711111111111%27%2C+%27state%27%3A+%27DU%27%2C+%27street1%27%3A+%27404%2C+11th+st%2C+void%27%7D&payment_info=%7B%27card_scheme%27%3A+%27Visa%27%2C+%27card_type%27%3A+%27Credit%27%2C+%27payment_description%27%3A+%274111+11%23%23+%23%23%23%23+1111%27%7D&payment_result=%7B%27acquirer_message%27%3A+%27ACCEPT%27%2C+%27acquirer_rrn%27%3A+%27014910159369%27%2C+%27response_code%27%3A+%27831000%27%2C+%27response_message%27%3A+%27Authorised%27%2C+%27response_status%27%3A+%27A%27%2C+%27transaction_time%27%3A+%272020-05-28T14%3A35%3A38%2B04%3A00%27%7D&tran_ref=TST2014900000688
http_build_query()
现在,关于和选项之间的区别,当通过在线工具urlencode()
运行上述 PHP 和 Python 示例输出的查询字符串时,以下是解码后的查询字符串:
PHP版本输出:
cart_amount=1&cart_currency=AED&cart_description=Sample+Payment&cart_id=Sample+Payment&customer_details[city]=Dubai&customer_details[country]=AE&customer_details[email]=jsmith@gmail.com&customer_details[ip]=127.0.0.1&customer_details[name]=John+Smith&customer_details[phone]=9711111111111&customer_details[state]=DU&customer_details[street1]=404,+11th+st,+void&payment_info[card_scheme]=Visa&payment_info[card_type]=Credit&payment_info[payment_description]=4111+11##+####+1111&payment_result[acquirer_message]=ACCEPT&payment_result[acquirer_rrn]=014910159369&payment_result[response_code]=831000&payment_result[response_message]=Authorised&payment_result[response_status]=A&payment_result[transaction_time]=2020-05-28T14:35:38+04:00&tran_ref=TST2014900000688
Python版本输出:
cart_amount=1&cart_currency=AED&cart_description=Sample+Payment&cart_id=Sample+Payment&customer_details={'city':+'Dubai',+'country':+'AE',+'email':+'jsmith@gmail.com',+'ip':+'127.0.0.1',+'name':+'John+Smith',+'phone':+'9711111111111',+'state':+'DU',+'street1':+'404,+11th+st,+void'}&payment_info={'card_scheme':+'Visa',+'card_type':+'Credit',+'payment_description':+'4111+11##+####+1111'}&payment_result={'acquirer_message':+'ACCEPT',+'acquirer_rrn':+'014910159369',+'response_code':+'831000',+'response_message':+'Authorised',+'response_status':+'A',+'transaction_time':+'2020-05-28T14:35:38+04:00'}&tran_ref=TST2014900000688
从这个输出来看,在我看来查询字符串(特别是 PHP[]
与 Python{}
项)的差异可能与 PHP 经常使用数组来处理结构化数据的方式有关,而 Python 也有能力使用字典之类的东西(相反到列表之类的东西)。我不知道这种差异是否会对您的用例产生影响,但我想指出即使 PHP 函数和使用的 Python 方法相似,生成的输出也可能会有所不同。
此外:
如果您不想在 Python 示例+
的查询字符串输出中出现符号,可以进行以下更改:
在上面的 Python 示例中更改此行:
from urllib.parse import urlencode
对此:
from urllib.parse import quote, urlencode
而这一行:
encoded = urlencode(results)
对此:
encoded = urlencode(results, quote_via=quote)
查询字符串输出应如下所示:
cart_amount=1&cart_currency=AED&cart_description=Sample%20Payment&cart_id=Sample%20Payment&customer_details=%7B%27city%27%3A%20%27Dubai%27%2C%20%27country%27%3A%20%27AE%27%2C%20%27email%27%3A%20%27jsmith%40gmail.com%27%2C%20%27ip%27%3A%20%27127.0.0.1%27%2C%20%27name%27%3A%20%27John%20Smith%27%2C%20%27phone%27%3A%20%279711111111111%27%2C%20%27state%27%3A%20%27DU%27%2C%20%27street1%27%3A%20%27404%2C%2011th%20st%2C%20void%27%7D&payment_info=%7B%27card_scheme%27%3A%20%27Visa%27%2C%20%27card_type%27%3A%20%27Credit%27%2C%20%27payment_description%27%3A%20%274111%2011%23%23%20%23%23%23%23%201111%27%7D&payment_result=%7B%27acquirer_message%27%3A%20%27ACCEPT%27%2C%20%27acquirer_rrn%27%3A%20%27014910159369%27%2C%20%27response_code%27%3A%20%27831000%27%2C%20%27response_message%27%3A%20%27Authorised%27%2C%20%27response_status%27%3A%20%27A%27%2C%20%27transaction_time%27%3A%20%272020-05-28T14%3A35%3A38%2B04%3A00%27%7D&tran_ref=TST2014900000688
解码后,应如下所示:
cart_amount=1&cart_currency=AED&cart_description=Sample Payment&cart_id=Sample Payment&customer_details={'city': 'Dubai', 'country': 'AE', 'email': 'jsmith@gmail.com', 'ip': '127.0.0.1', 'name': 'John Smith', 'phone': '9711111111111', 'state': 'DU', 'street1': '404, 11th st, void'}&payment_info={'card_scheme': 'Visa', 'card_type': 'Credit', 'payment_description': '4111 11## #### 1111'}&payment_result={'acquirer_message': 'ACCEPT', 'acquirer_rrn': '014910159369', 'response_code': '831000', 'response_message': 'Authorised', 'response_status': 'A', 'transaction_time': '2020-05-28T14:35:38+04:00'}&tran_ref=TST2014900000688
推荐阅读
- python - 使用 PARANOID 的 JSON 数据屏蔽
- eclipse-cdt - Eclipse CDT C++20 支持
- azure - azure ad b2c 自定义策略中的默认文本修改
- angular - 如何使用用户数据填充离子表单
- intellij-idea - 在 intellij 中运行应用程序没有重启按钮
- javascript - 如何使用 JavaScript 事件制作计算器?
- python - 如何在 Django 中使用 BeautifulSoup 从嵌套的 HTML 中获取数据
- mysql - 连接后无法在 mysql 工作台中运行 sql 脚本
- codeceptjs - 使用 jenkins 运行 codecept
- sql - 查找日期是否在两个日期之间的 SQL 语句