Interface Python para PayPal - Urllib.urlencode não personagens não -ASCII Falha
Pergunta
Estou tentando implementar a funcionalidade do PayPal IPN. O protocolo básico é como tal:
- O cliente é redirecionado do meu site para o site do PayPal para concluir o pagamento. Ele faz login em sua conta, autoriza o pagamento.
- O PayPal liga para uma página no meu servidor que passa em detalhes como postagem. Os detalhes incluem o nome, endereço e informações de pagamento de uma pessoa etc.
- Preciso ligar para um URL no site do PayPal internamente da minha página de processamento, passando de volta todos os parâmetros que foram aprovados no ABOVEM e um adicional chamado 'CMD' com um valor de '_notify-validate'.
Quando eu tento urllib.urlencode os parâmetros que o PayPal me enviou, eu recebo um:
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)
Entendo que o Urlencode faz a codificação ASCII e, em certos casos, as informações de contato de um usuário podem conter caracteres não-ASCII. Isto é incompreensível. Minha pergunta é: como codificar caracteres não-ASCII para postar em um URL usando urllib2.urlopen (req) (ou outro método)
Detalhes:
Eu li os parâmetros na solicitação original do PayPal da seguinte forma (o GET é para teste):
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()
O código que eu uso para enviar de volta a solicitação ao PayPal da página de processamento é:
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
Obviamente, o problema só surge se o nome ou endereço de alguém ou outro campo usado para o pagamento do PayPal não cair na faixa ASCII.
Solução
Tente converter o dicionário de parâmetros em UTF-8 primeiro ... Urlencode parece assim melhor do que Unicode:
params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in params.items()))
Obviamente, isso pressupõe que sua entrada seja unicode. Se sua entrada for algo diferente do Unicode, você desejará decodificá -lo para unicode primeiro e depois codificá -lo:
params['foo'] = my_raw_input.decode('iso-8859-1')
params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in params.items()))
Outras dicas
Em vez de codificar para utf-8
, deve -se codificar ao que o PayPal está usando para a postagem. Ele está disponível em Key 'Charset' no formulário PayPal envia.
Então, o código a seguir funcionou para mim:
data = dict([(k, v.encode(data['charset'])) for k, v in data.items()])
Eu sei que é um pouco tarde para o Chime aqui, mas a melhor solução que encontrei foi nem sequer analisar o que eles estavam retribuindo. Em Django (não sei o que você está usando), consegui receber a solicitação bruta que eles enviaram, que eu passei de volta literalmente. Então foi apenas uma questão de colocar a chave do CMD nisso.
Dessa forma, nunca importa o que codificando que eles enviam, você está apenas enviando de volta.