这一篇教程,我们一起通过使用支付宝官方的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('')
项目源代码下载:【点此下载】