Python实现PayFast API支付时签名验证失败:生成签名与提交签名不匹配
Python实现PayFast API支付时签名验证失败:生成签名与提交签名不匹配
我太懂你现在的烦躁了——明明照着PayFast的文档写了签名生成代码,却一直被400 signature: Generated signature does not match submitted signature.的错误卡着,试了大小写、参数顺序都不管用,简直头大。我帮你梳理几个最可能的问题点,你逐一排查下:
1. 最容易踩坑:参数顺序完全不对
PayFast的签名生成对参数顺序有严格要求,必须和官方文档里列出的字段顺序完全一致,而不是你字典的插入顺序或者任意顺序。你现在用data.items()遍历拼接参数,哪怕Python 3.7+的字典是插入有序,但如果你的data字典的键顺序和PayFast要求的不一样,拼接出来的参数字符串就会和官方预期的不同,签名自然匹配不上。
解决方法:固定参数顺序
你需要先按照PayFast文档里的字段顺序定义一个列表,然后严格按照这个顺序遍历拼接参数,比如:
# 严格对应PayFast文档的字段顺序,用到的字段必须按这个顺序加 PAYFAST_PARAM_ORDER = [ "MERCHANT_ID", "MERCHANT_KEY", "RETURN_URL", "CANCEL_URL", "NOTIFY_URL", "EMAIL_ADDRESS", "AMOUNT", "ITEM_NAME", "ITEM_DESCRIPTION" # 其他用到的字段一定要按文档顺序补充 ] def generate_signature(data: dict, passphrase: str) -> str: encoded_params = [] # 按固定顺序遍历参数 for param_key in PAYFAST_PARAM_ORDER: # 假设你的data字典用的是小写键,比如"merchant_id",如果是大写就直接用param_key value = data.get(param_key.lower()) if value: # 忽略空值 encoded_value = urllib.parse.quote_plus(str(value)).upper() encoded_params.append(f"{param_key}={encoded_value}") # 后续的passphrase处理和哈希逻辑不变...
2. 特殊字符未正确编码(你的输出里已经有明显问题)
看你输出的待哈希字符串:
...&ITEM_NAME=COOL&THING&ITEM_DESCRIPTION=BLAH&BLAH&BLAH...
这里ITEM_NAME的值里的&没有被编码成%26,直接导致ITEM_NAME被截断成COOL,还多了无效的THING参数——这完全破坏了参数字符串的结构,签名肯定错。
你单独测试下这段代码:
import urllib.parse print(urllib.parse.quote_plus("COOL&THING").upper())
正常输出应该是COOL%26THING,如果不是,要么你的data里的ITEM_NAME值本身有问题,要么是编码逻辑哪里漏了。
3. 其他容易忽略的细节
- Passphrase的一致性:确认你用的Passphrase和PayFast后台(沙箱/生产)设置的完全一致,没有多余空格、大小写错误,而且要确保是对应环境的Passphrase(沙箱和生产的Passphrase是分开的)。
- 空值处理:你已经忽略了空值,这是对的,但要确保没有把空值不小心加入到参数字符串里。
- 参数大小写:PayFast要求参数键是大写,你的代码里已经把键转成大写了,这个没问题,但要确保所有键的大写是正确的(比如
PASSPHRASE而不是Passphrase)。
调试小技巧
- 把你生成的
param_string复制出来,手动用在线MD5工具计算哈希,看和代码生成的是否一致,排除哈希计算的问题。 - 找一个PayFast官方的签名测试案例,把参数代入你的代码,看生成的签名是否和官方示例一致,快速定位是代码逻辑还是参数的问题。
修改后的完整代码参考:
import hashlib import urllib.parse # 严格按照PayFast文档指定的参数顺序排列 PAYFAST_PARAM_ORDER = [ "MERCHANT_ID", "MERCHANT_KEY", "RETURN_URL", "CANCEL_URL", "NOTIFY_URL", "EMAIL_ADDRESS", "AMOUNT", "ITEM_NAME", "ITEM_DESCRIPTION" ] def generate_signature(data: dict, passphrase: str) -> str: """ Generates an MD5 signature for PayFast API request. - Follows strict parameter order as per PayFast docs - URL encodes values with uppercase encoding. - Uses '+' for spaces. - Encodes passphrase before appending. """ encoded_params = [] # 按固定顺序遍历参数,确保与文档要求一致 for param_key in PAYFAST_PARAM_ORDER: value = data.get(param_key.lower()) if value: # Ignore empty values encoded_value = urllib.parse.quote_plus(str(value)).upper() encoded_params.append(f"{param_key}={encoded_value}") # URL-encode the passphrase as well encoded_passphrase = urllib.parse.quote_plus(passphrase).upper() # Concatenate parameters param_string = "&".join(encoded_params) # Append encoded passphrase param_string += f"&PASSPHRASE={encoded_passphrase}" print(f"String before hashing: {param_string}") # Generate MD5 hash signature = hashlib.md5(param_string.encode("utf-8")).hexdigest() print(f"Generated Signature: {signature}") return signature
备注:内容来源于stack exchange,提问作者user8188120




