首页 > 解决方案 > 在嵌套字典上用 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 小时我一直在尝试自己重新创建它,但无济于事。

我坚持的部分是ksorthttp_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 中有现成的方法可以实现这一点,那将非常有帮助。

一如既往地感谢。

标签: pythonphpnestedresponse

解决方案


针对您关于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

推荐阅读