最新消息:欢迎光临 魔力 • Python!大家可以点开导航菜单中的【学习目录】,这个目录类似图书目录,更加方便学习!

Python3.6与Django2实现支付宝网站支付与回调验签

Django教程 小楼一夜听春语 14179浏览 0评论

这一篇教程,我们一起通过使用支付宝官方的SDK实现支付和支付结果验证。

一、准备工作

在正式开始之前,需要做一些准备,包括:

1、生成RSA密钥:https://docs.open.alipay.com/291/105971/

密钥格式选择“PKCS1(非Java适用)”,密钥长度选择“2048”。

2、设置沙箱环境:https://openhome.alipay.com/platform/appDaily.htm

复制保存“APPID”和“支付网关”信息,然后,将上一步生成的应用公钥填入,并将支付宝公钥也复制保存。

除以上设置外,其他无需设置。

沙箱环境是开发过程中可以反复测试使用的虚拟支付环境,开发完成后,在生产环境中将APPID和支付宝网关改为实际应用APPID以及支付宝正式网关即可。

3、手机安装支付宝APP(沙箱版)

沙箱环境页面点击“沙箱工具”,下载安装沙箱钱包,用于扫码支付测试。

下载地址:https://sandbox.alipaydev.com/user/downloadApp.htm

或者扫描二维码安装:

在沙箱环境页面再点击“沙箱账号”,根据买家信息中的账号、登录密码登录沙箱钱包,别忘了给沙箱钱包输入金额进行充值。

完成以上准备之后,接下来我们正式开始支付宝接口的调用。

二、安装官方SDK

支付宝官方在2018年5月23日,发布了一个Python版的SDK(公测版),之后在2018年7月20日发布了正式版(3.1.6)。

本文基于正式版为大家进行介绍。

我们可以通过“pip”命令进行安装。

不过这个SDK依赖Pycrypto,所以建议先进行Pycrypto的安装。

pip install pycrypto

如果是Windows系统,到这里可能安装出错了,可能出现的错误如下:

  • error: command ‘cl.exe’ failed: No such file or directory
  • error: command ‘C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\x86_amd64\\cl.exe’ failed with exit status 2
  • error: Microsoft Visual C++ 14.0 is required. Get it with “Microsoft Visual C++ Build Tools”: http://landinghub.visualstudio.com/visual-cpp-build-tools

解决这些错误,需要安装Visual Studio 2015。

下载地址:https://pan.baidu.com/s/1Bz8zB2et71xxyTaCUuU36A

下载之后,双击进行安装,在组件选择页面我们只选择需要的组件。

因为,我们只是下载了安装器,勾选组件之后,安装器会下载相应的组件进行安装,不过即便这样安装过程也非常漫长。

完成安装之后,我们进行环境变量中用户变量(不是系统变量)的设置。

变量名称:VCINSTALLDIR

变量值:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC

接下来,打开cmd,执行如下命令:

set CL=/FI"%VCINSTALLDIR%\\INCLUDE\\stdint.h" %CL%

注意:如果是虚拟环境,请在虚拟环境的命令行中输入此命令,并且命令中的符号都是英文半角的,网上有的资料中引号是中文符号,导致仍然无法安装Pycrypto(我特么就是受害者…)。

如果还报错误“src/winrand.c: fatal error C1083: Cannot open include file: ‘%VCINSTALLDIR%\\INCLUDE\\stdint.h’: No such file or directory”,可以尝试把命令改为绝对路径。

先通过“set CL=”命令,将已有设置清空。

然后,执行命令:

set CL=/FI"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\\INCLUDE\\stdint.h" %CL%

完成上述步骤之后,重新执行安装Pycrypto的命令,就应该不出什么问题了。

继续安装alipay-sdk-python。

pip install alipay-sdk-python

安装过程中会自动安装一些依赖项,如果上面没有安装Pycrypto,此时也会自动进行安装。

三、使用Django2创建支付项目

1、创建一个模板用于提交订单。

示例代码:(buy.html)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>结算</title>
</head>
<body>
<form action="../to_pay/" method="get">
    <p>商品:Python 入门视频</p>
    <input type="hidden" name="goods_name" value="Python 入门视频">
    <p>价格:¥29.00</p>
    <input type="hidden" name="price" value="29.00">
    数量:<input type="number" name="quantity" value="3">
    <p><input type="submit"></p>
</form>
</body>
</html>

2、添加URL配置

这里需要3个URL,分别用于打开购买页面、唤起支付页面和回调通知验签

示例代码:(urls.py)

path('buy/', site_view.buy),  # 打开购买页面
path('to_pay/', site_view.to_pay),  # 唤起支付页面
path('pay_result/', site_view.pay_result),  # 回调通知验签

3、添加相关设置

示例代码:(settings.py)

ALLOWED_HOSTS = ['*'] # 外网访问需设置,填写域名或服务器IP地址,“*”表示允许所有访问(不建议生产环境使用)。

INSTALLED_APPS = [
 ...省略部分代码...
 'website',
]

MIDDLEWARE = [
 ...省略部分代码...
 # 'django.middleware.csrf.CsrfViewMiddleware', # 注释这一行
 ...省略部分代码...
]

# APPID
ALIPAY_APPID = "2016*********590" # 沙箱APPID,生产环境须更改为应用APPID。

# 网关
ALIPAY_URL = "https://openapi.alipaydev.com/gateway.do" # 沙箱网关,生产环境须更改为正式网关。
# ALIPAY_URL = "https://openapi.alipay.com/gateway.do" # 正式网关,开发环境勿使用。

# 回调通知地址
# ALIPAY_NOTIFY_URL = "http://127.0.0.1:8888/pay_result/" # 如果只可以内网访问开发服务器
ALIPAY_NOTIFY_URL = "http://1.203.45.678:8888/pay_result/" # 如果生产环境或外网可以访问开发服务器
ALIPAY_RETURN_URL = "http://1.203.45.678:8888/pay_result/" # return_url可以单独设置,本文中保持一致。

# 使用密钥文件
APP_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'website/keys/app_private_key.txt')
ALIPAY_PUBLIC_KEY_PATH = os.path.join(BASE_DIR, 'website/keys/alipay_public_key.txt')

# 使用密钥字符串
APP_PRIVATE_KEY = 'MIIEowIBAAKCAQEA0v793k1...省略...' # 应用私钥
ALIPAY_PUBLIC_KEY = 'MIIBIjANBgkqhkiG9w0BAQEFAA...省略...' # 支付宝公钥

在上方设置中,回调通知地址需要视网络状况而决定。

如果启动开发服务器之后,通过外网IP(百度搜索“IP”即可看到)可以进行访问,就使用外网地址。

如果只能通过内网访问,那就只能使用本机地址。

一般来说,外网不能访问可以尝试按以下方法解决。

(1)联系宽带服务商,要求转为公网IP。服务商一般不会拒绝这个要求,只是需要等待1~2天。

(2)为无线路由器设置端口转发(端口映射)或者开启DMZ功能。

4、定义打开购买页面的视图函数

示例代码:(views.py)

from django.shortcuts import render
def buy(request):
    return render(request, 'buy.html')

5、定义唤起支付页面的视图函数

示例代码:(views.py)

from django.shortcuts import HttpResponseRedirect  
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig  # 客户端配置类
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient  # 默认客户端类
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel  # 网站支付数据模型类
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest  # 网站支付请求类
from myproject import settings
import random
def to_pay(request):
    alipay_client_config = AlipayClientConfig()  # 创建配置对象
    alipay_client_config.server_url = settings.ALIPAY_URL  # 网关
    alipay_client_config.app_id = settings.ALIPAY_APPID  # APPID
    alipay_client_config.app_private_key = settings.APP_PRIVATE_KEY  # 应用私钥
    client = DefaultAlipayClient(alipay_client_config=alipay_client_config)  # 使用配置创建客户端
    model = AlipayTradePagePayModel()  # 创建网站支付模型
    model.out_trade_no = 'order_%s' % random.randrange(100000, 999999)  # 商户订单号码
    model.total_amount = int(request.GET['quantity']) * float(request.GET['price'])  # 支付总额
    model.subject = request.GET['goods_name']  # 订单标题
    model.body = '一套完整详细的Python入门视频。'  # 订单描述
    model.product_code = 'FAST_INSTANT_TRADE_PAY'  # 与支付宝签约的产品码名称,目前只支持这一种。
    model.timeout_express = '30m'  # 订单过期关闭时长(分钟)
    pay_request = AlipayTradePagePayRequest(biz_model=model)  # 通过模型创建请求对象
    pay_request.notify_url = settings.ALIPAY_NOTIFY_URL  # 设置回调通知地址(POST)
    pay_request.return_url = settings.ALIPAY_RETURN_URL # 设置回调通知地址(GET)

    response = client.page_execute(pay_request, http_method='GET')  # 获取支付链接
    return HttpResponseRedirect(response)  # 重定向到支付宝支付页面

上方代码中,支付模型负责组织支付链接参数,包括公共参数和请求参数。

所有参数可以参考官方文档:https://docs.open.alipay.com/270/alipay.trade.page.pay

完成以上代码后,如果没有问题,已经可以在购买页面点击提交按钮打开支付宝付款页面了。

6、定义检查支付结果的函数

当我们使用沙箱钱包扫码完成支付,如果设置了回调通知地址,此时支付宝会有两个回调通知:同步通知和异步通知。

不同的是:

  • 同步通知会在支付完成后,跳转到回调通知地址页面,并以GET方式传递参数。
  • 异步通知会在支付完成后,向回调通知地址发出POST请求,同时传递参数字典。

不管是同步通知还是异步通知,我们都需要对通知中的签名(sign)进行校验,以确认该通知来自支付宝,保障信息安全。

在支付宝SDK中,为我们提供了验签的函数,我们只需要组织参数进行验签。

因为,两种回调通知的验签参数处理过程都是一样的,只是参数分别来自于GET和POST,所以我们可以只定义1个检查支付结果的函数。

参考官方的验签过程,取出的回调参数后,必须经过以下处理。

  • 参数字典去除签名(sign)和签名类型(sign_type);
  • 剩余参数的字典进行按字母升序排序,形成列表;
  • 将排序后的列表转换为二进制参数字符串。
  • 将支付宝公钥、参数字符串和签名传入验签函数进行验签,验签通过返回值为True,否则会引发异常。

示例代码:(views.py)

from alipay.aop.api.util.SignatureUtils import verify_with_rsa
def check_pay(params):  # 定义检查支付结果的函数
    sign = params.pop('sign', None)  # 取出签名
    params.pop('sign_type')  # 取出签名类型
    params = sorted(params.items(), key=lambda e: e[0], reverse=False)  # 取出字典元素按key的字母升序排序形成列表
    message = "&".join(u"{}={}".format(k, v) for k, v in params).encode()  # 将列表转为二进制参数字符串
    # with open(settings.ALIPAY_PUBLIC_KEY_PATH, 'rb') as public_key: # 打开公钥文件
    try:
        #     status =verify_with_rsa(public_key.read().decode(),message,sign) # 验证签名并获取结果
        status = verify_with_rsa(settings.ALIPAY_PUBLIC_KEY.encode('utf-8').decode('utf-8'), message,
                                 sign)  # 验证签名并获取结果
        return status  # 返回验证结果
    except:  # 如果验证失败,返回假值。
        return False

7、创建不同回调方式的响应结果

同步通知的响应结果,我们需要返回内容到页面。

异步通知的响应结果,我们需要返回内容到支付宝服务器。

这里特别注意,异步通知如果没有向支付宝返回字符串“success”的话,会在一定时间内不停的发起回调请求,直到超出时限或者接收到字符串“success”为止。

示例代码:(views.py)

from django.shortcuts import HttpResponse
def pay_result(request):  # 定义处理回调通知的函数
    if request.method == 'GET':
        params = request.GET.dict()  # 获取参数字典
        if check_pay(params):  # 调用检查支付结果的函数
            '''
                此处编写支付成功后的业务逻辑
            '''
            return HttpResponse('支付成功!')
        else:
            '''
                此处编写支付失败后的业务逻辑
            '''
            return HttpResponse('支付失败!')
    if request.method == 'POST':
        params = request.POST.dict()  # 获取参数字典
        if check_pay(params):  # 调用检查支付结果的函数
            '''
                此处编写支付成功后的业务逻辑
            '''
            # print('支付成功!')
            return HttpResponse('success')  # 返回成功信息到支付宝服务器
        else:
            '''
                此处编写支付失败后的业务逻辑
            '''
            return HttpResponse('')

项目源代码下载:【点此下载

转载请注明:魔力Python » Python3.6与Django2实现支付宝网站支付与回调验签

头像
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网站 (可选)

网友最新评论 (10)

  1. 头像
    请问个人支付宝账号能作为收款账户吗
    Django爱好者6年前 (2018-12-14)回复
    • 小楼一夜听春语
      不能
      小楼一夜听春语6年前 (2018-12-15)回复
  2. 头像
    仔细检查过配置没有发现异常,支付可以成功,但是回调的验证一直不能通过是什么原因呢?
    逗号6年前 (2018-12-21)回复
    • 小楼一夜听春语
      检查你的回调地址
      小楼一夜听春语6年前 (2018-12-21)回复
  3. 头像
    请问,我按上面的步骤做的,为什么我支付完,处理回调通知的函数响应不了? 谢谢
    Django初学者6年前 (2019-04-20)回复
  4. 头像
    我的回调地址是 "http://127.0.0.1:8000/pay_result/",觉得应该不是回调地址的问题
    Django初学者6年前 (2019-04-20)回复
  5. 头像
    你好,他的这个同步通知和异步通知是同时存在的么,是都会发起么
    itachi5年前 (2019-11-01)回复
  6. 头像
    [2020-01-10 10:39:17,922] - ERROR - [django.request] Internal Server Error: /pay/to_pay/ Traceback (most recent call last): File "C:\chat\test_chats\chat\venv\lib\site-packages\alipay\aop\api\DefaultAlipayClient.py", line 125, in __prepare_request_params sign = sign_with_rsa2(self.__config.app_private_key, sign_content, self.__config.charset) File "C:\chat\test_chats\chat\venv\lib\site-packages\alipay\aop\api\util\SignatureUtils.py", line 49, in sign_with_rsa2 signature = rsa.sign(sign_content, rsa.PrivateKey.load_pkcs1(private_key, format='PEM'), 'SHA-256') File "C:\chat\test_chats\chat\venv\lib\site-packages\rsa\key.py", line 118, in load_pkcs1 return method(keyfile) File "C:\chat\test_chats\chat\venv\lib\site-packages\rsa\key.py", line 559, in _load_pkcs1_pem der = rsa.pem.load_pem(keyfile, b'RSA PRIVATE KEY') File "C:\chat\test_chats\chat\venv\lib\site-packages\rsa\pem.py", line 99, in load_pem return base64.standard_b64decode(pem) File "C:\Users\90607\AppData\Local\Continuum\anaconda3\lib\base64.py", line 105, in standard_b64decode return b64decode(s) File "C:\Users\90607\AppData\Local\Continuum\anaconda3\lib\base64.py", line 87, in b64decode return binascii.a2b_base64(s) binascii.Error: Invalid base64-encoded string: number of data characters (1657) cannot be 1 more than a multiple of 4 During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\chat\test_chats\chat\venv\lib\site-packages\django\core\handlers\exception.py", line 35, in inner response = get_response(request) File "C:\chat\test_chats\chat\venv\lib\site-packages\django\core\handlers\base.py", line 128, in _get_response response = self.process_exception_by_middleware(e, request) File "C:\chat\test_chats\chat\venv\lib\site-packages\django\core\handlers\base.py", line 126, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "C:\chat\test_chats\chat\pay\views.py", line 256, in to_pay response = client.page_execute(pay_request, http_method='GET') # 获取支付链接 File "C:\chat\test_chats\chat\venv\lib\site-packages\alipay\aop\api\DefaultAlipayClient.py", line 234, in page_execute query_string, params = self.__prepare_request(request) File "C:\chat\test_chats\chat\venv\lib\site-packages\alipay\aop\api\DefaultAlipayClient.py", line 89, in __prepare_request common_params, params = self.__prepare_request_params(request) File "C:\chat\test_chats\chat\venv\lib\site-packages\alipay\aop\api\DefaultAlipayClient.py", line 129, in __prepare_request_params raise RequestException('[' + THREAD_LOCAL.uuid + ']request sign failed. ' + str(e)) alipay.aop.api.exception.Exception.RequestException: [64e61190-3352-11ea-9910-ec5c68b7d9b4]request sign failed. Invalid base64-encoded string: number of data characters (1657) cannot be 1 more than a multiple of 4 [2020-01-10 10:39:18,828] - ERROR - [django.server] "GET /pay/to_pay/?goods_name=Python+%E5%85%A5%E9%97%A8%E8%A7%86%E9%A2%91&price=29.00&quantity=1 HTTP/1.1" 500 142848 pydev debugger: process 19064 is connecting
    沙发5年前 (2020-01-10)回复
  7. 头像
    请问按照上面的配置,报这个错是什么原因
    沙发5年前 (2020-01-10)回复
    • 小楼一夜听春语
      签名错误
      小楼一夜听春语5年前 (2020-01-17)回复