Python 对 PayPal 的接口 - urllib.urlencode 非 ASCII 字符失败

20 投票
3 回答
14043 浏览
提问于 2025-04-15 11:14

我正在尝试实现PayPal的IPN功能。这个基本流程是这样的:

  1. 客户从我的网站被重定向到PayPal的网站完成支付。他登录自己的账户,授权付款。
  2. PayPal会调用我服务器上的一个页面,并通过POST方式传递一些信息。信息包括一个人的姓名、地址和支付信息等。
  3. 我需要在我的处理页面内部调用PayPal网站上的一个URL,把上面传递来的所有参数和一个额外的参数'cmd'(值为'_notify-validate')一起发送回去。

当我尝试使用urllib.urlencode对PayPal发送给我的参数进行编码时,我遇到了一个问题:

While calling send_response_to_paypal. Traceback (most recent call last):
  File "<snip>/account/paypal/views.py", line 108, in process_paypal_ipn
    verify_result = send_response_to_paypal(params)
  File "<snip>/account/paypal/views.py", line 41, in send_response_to_paypal
    params = urllib.urlencode(params)
  File "/usr/local/lib/python2.6/urllib.py", line 1261, in urlencode
    v = quote_plus(str(v))
UnicodeEncodeError: 'ascii' codec can't encode character u'\ufffd' in position 9: ordinal not in range(128)

我知道urlencode会进行ASCII编码,而在某些情况下,用户的联系信息可能包含非ASCII字符。这是可以理解的。我的问题是,如何使用urllib2.urlopen(req)(或其他方法)对非ASCII字符进行编码,以便POST到一个URL?

详细信息:

我读取PayPal原始请求中的参数如下(GET是为了测试):

def read_ipn_params(request):
    if request.POST:  
        params= request.POST.copy()  
        if "ipn_auth" in request.GET:
            params["ipn_auth"]=request.GET["ipn_auth"]
        return params
    else:  
        return request.GET.copy()  

我用来从处理页面发送请求回PayPal的代码是:

def send_response_to_paypal(params):
    params['cmd']='_notify-validate'  
    params = urllib.urlencode(params)  
    req = urllib2.Request(PAYPAL_API_WEBSITE, params)  
    req.add_header("Content-type", "application/x-www-form-urlencoded") 
    response = urllib2.urlopen(req)  
    status = response.read()  
    if not status == "VERIFIED":  
        logging.warn("PayPal cannot verify IPN responses: " + status)
        return False

    return True

显然,只有当某人的姓名、地址或其他用于PayPal支付的字段不在ASCII范围内时,问题才会出现。

3 个回答

3

我知道现在说这个有点晚,但我找到的最好解决办法就是根本不去解析他们返回的内容。在Django(我不知道你用的是什么)中,我能够获取他们发送的原始请求,然后直接把它原封不动地返回。接下来只需要在这个请求上加上cmd这个键就行了。

这样一来,他们发送的编码格式就不重要了,因为你只是把它直接发回去。

6

与其把内容编码成 utf-8,不如直接编码成 PayPal 用于发送数据的格式。这个格式可以在 PayPal 发送的表单中找到,键名是 'charset'。

所以,下面这段代码对我来说是有效的:

data = dict([(k, v.encode(data['charset'])) for k, v in data.items()])

41

先试着把参数字典转换成utf-8格式……urlencode似乎更喜欢这个格式,而不是unicode:

params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in params.items()))

当然,这里假设你的输入是unicode格式。如果你的输入不是unicode格式,你需要先把它解码成unicode,然后再进行编码:

params['foo'] = my_raw_input.decode('iso-8859-1')
params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in params.items()))

撰写回答