Python接口与PayPal - urllib.urlencode无法处理非ASCII字符

20

我正在尝试实现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个回答

41

尝试先将params字典转换为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()))

你是正确的 - 这确实解决了URLEncode的异常问题。然而,现在PayPal给我返回一个无效的响应。他们真是让人头疼... - Krystian Cybulski
1
所以,krys,如果PayPal要求的编码不是UTF-8,他们会文档化吗? - Alex Martelli
谢谢!小bug:在你给出的第一个例子末尾有一个多余的]。 - Emil Stenström
3
谢谢你提供这么棒的答案,它真的帮助我解决了我在PayPal IPN上遇到的问题。不过在我的情况下,还有两个问题让我困扰了一段时间。(1) 需要告诉PayPal使用UTF-8进行传输。这可以在PayPal->配置文件->我的销售工具->PayPal按钮语言编码->更多选项->UTF-8中完成。(2) 另外值得注意的是,在沙盒环境中,选择UTF-8编码没有任何效果(它仍然使用其他编码),但在生产环境中可以正常工作。 - Alexander Marquardt
非常感谢您发布解决方案,这也适用于Django的Satchmo,对于可能遇到问题的任何人都有用。 - leech

6

不要将编码设置为utf-8,而应该将其设置为PayPal在邮寄中使用的任何编码方式。此编码方式可以在PayPal发送的表单中通过键“charset”获取。

因此,以下代码适用于我:

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


3
我知道现在加入讨论有点晚了,但我找到的最好解决方案是不要解析他们返回的内容。在django中(不知道你使用的是什么),我能够获取他们发送的原始请求,然后完全按照原样传回去。然后只需要将cmd键放在上面即可。
这样无论他们发送给你什么编码,你都只是将它直接发送回去,不需要担心编码问题。

你尝试过使用包含像ñ、á、é等Unicode字符的IPN响应吗?这个问题只会出现在这些类型的字符中。 - Alexander Marquardt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接