You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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)。

调试小技巧

  1. 把你生成的param_string复制出来,手动用在线MD5工具计算哈希,看和代码生成的是否一致,排除哈希计算的问题。
  2. 找一个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

火山引擎 最新活动