我们在使用Django构建网站时常需要对接第三方支付平台的支付接口,这里就以支付宝为例(其他平台大同小异),使用支付宝开放平台的沙箱环境进行实验。
我们这里使用一个第三方的AliPay Python SDK
(github )
下面看一下它的基本使用
调用流程
事实上需要我们网站服务端做的事并不多,只需要生成一个订单向支付宝发出支付请求,等用户支付完毕后向支付宝(通过同步和异步的方式)查询订单、交易信息即可。
在实际生产环境中,需要注意如下各种安全性问题:
由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。
商户系统接收到异步通知以后,必须通过验签(验证通知中的 sign 参数)来确保支付通知是由支付宝发送的。
接收到异步通知并验签通过后,请务必核对通知中的 app_id、out_trade_no、total_amount 等参数值是否与请求中的一致,并根据 trade_status 进行后续业务处理。
在支付宝端,partnerId 与 out_trade_no 唯一对应一笔单据,商户端保证不同次支付 out_trade_no 不可重复;若重复,支付宝会关联到原单据,基本信息一致的情况下会以原单据为准进行支付。
具体实践 1.准备工作 由于使用真实环境需要商户支付宝账号、上线应用需要审批等流程,我们这里使用支付宝开放平台的沙箱环境
沙箱环境中提供了后面需要的参数如APPID
、APP_PRIVATE_KEY
、ALIPAY_PUBLIC_KEY
、支付宝网关
等。
接下来安装AliPay Python SDK
1 pip3 install python-alipay-sdk --upgrade
由于是沙箱环境,平台已经提供给我们需要的公钥和私钥,如果是生产环境,则需要通过openssl生成
1 2 3 4 openssl OpenSSL> genrsa -out app_private_key.pem 2048 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem OpenSSL> exit
在支付宝上下载的公钥是一个字符串,你需要在文本的首尾添加标记位:
1 -----BEGIN PUBLIC KEY----- 和 -----END PUBLIC KEY-----
2.创建订单 先在settings.py
中设定一些关键参数
1 2 3 4 5 6 7 8 9 app_private_key_string = open ("/path/to/your/private/key.pem" ).read() alipay_public_key_string = open ("/path/to/alipay/public/key.pem" ).read() ALIPAY_APP_ID = "2021000120607609" RETURN_URL = "http://xxx.xxx.xxx.xxx/" GATEWAY = "https://openapi.alipaydev.com/gateway.do?"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from alipay import AliPay, AliPayConfigfrom .settings import APP_PRIVATE_KEY, ALIPAY_PUBLIC_KEY, ALIPAY_APP_ID, RETURN_URL, GATEWAYdef create_alipay (): alipay = AliPay( appid=ALIPAY_APP_ID, app_notify_url=None , app_private_key_string=APP_PRIVATE_KEY, alipay_public_key_string=ALIPAY_PUBLIC_KEY, sign_type="RSA2" , debug=False , verbose=False , config=AliPayConfig(timeout=15 ) ) return alipay
下面可以创建支付订单了(官方文档 )
1 2 3 4 5 6 7 8 9 10 11 12 def alipay_pay (subject, total_amount, out_trade_no, return_url_view ): alipay = create_alipay() return_url = RETURN_URL + return_url_view order_string = alipay.api_alipay_trade_page_pay( out_trade_no=out_trade_no, total_amount=total_amount, subject=subject, return_url=return_url, notify_url="https://example.com/notify" ) return order_string
调用
1 2 3 4 5 6 7 8 9 10 11 import stringimport randomfrom .settings import GATEWAYout_trade_no = "" .join(random.sample(string.ascii_letters+string.digits, 32 )) order_string = alipay_pay(subject="测试商品" ,total_amount=100 ,out_trade_no=out_trade_no,return_url_view='alipay_return' ) return HttpResponseRedirect(GATEWAY+order_string)
调用后会跳转到支付宝平台,使用沙箱环境提供的买家账号即可完成支付
但是此时我们还不能回调跳转到我们自己的网站,也不能获得订单支付信息,下面还有最后一步。
3.同步回调 我们刚刚创建的订单信息中填写了return_url
,我们需要一个视图函数来接收,并对其返回值进行分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def alipay_return (request ): processed_dict = {} for key, value in request.GET.items(): processed_dict[key] = value """ processed_dict = { 'charset': 'utf-8', 'out_trade_no': 'xxxxxxx', # 这个是我们之前创建订单时生成的商户交易号 'method': 'alipay.trade.page.pay.return', 'total_amount': '100.00', # 交易金额 'trade_no': '20220xxxxxxxx24353', # 支付宝交易号 'auth_app_id': '2021xxxxxx609', # 用户appid 'version': '1.0', 'app_id': '2021xxxxxx7609', # 沙箱提供的APPID 应用ID 'sign_type': 'RSA2', 'seller_id': '2088xxxxx844', # 收款支付宝账号对应的支付宝唯一用户号。 以2088开头的纯16位数字 'timestamp': '2022-05-28 23:40:55' } """ sign = processed_dict.pop("sign" , None ) new_alipay = create_alipay() verify_re = new_alipay.verify(processed_dict, sign) if verify_re is True : print ("支付成功" ) else : print ("支付失败" )
注意:同步回调往往不可靠,因此需要增加一个异步回调检验
另外,在订单创建后需要向数据库存储订单信息,包括订单金额、商户订单号、appid等,等待回调后与参数校验一致无误后再将订单支付信息进行更新。下面的完整示例不会包括该部分,请自行完成
完整示例 项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from alipay import AliPay, AliPayConfigfrom .settings import APP_PRIVATE_KEY, ALIPAY_PUBLIC_KEY, ALIPAY_APP_ID, RETURN_URLdef create_alipay (): alipay = AliPay( appid=ALIPAY_APP_ID, app_notify_url=None , app_private_key_string=APP_PRIVATE_KEY, alipay_public_key_string=ALIPAY_PUBLIC_KEY, sign_type="RSA2" , debug=False , verbose=False , config=AliPayConfig(timeout=15 ) ) return alipay def alipay_pay (subject, total_amount, out_trade_no, return_url_view ): alipay = create_alipay() return_url = RETURN_URL + return_url_view order_string = alipay.api_alipay_trade_page_pay( out_trade_no=out_trade_no, total_amount=total_amount, subject=subject, return_url=return_url, notify_url="https://example.com/notify" ) return order_string
1 2 3 4 5 6 7 8 9 ... ... ALIPAY_APP_ID = "xxxxxx" APP_PRIVATE_KEY = open (os.path.join(BASE_DIR, 'alipay/app_private_key.pem' ), 'r' ).read() ALIPAY_PUBLIC_KEY = open (os.path.join(BASE_DIR, 'alipay/alipay_public_key.pem' ), 'r' ).read() RETURN_URL = "http://xxxxxx/" GATEWAY = "https://openapi.alipaydev.com/gateway.do?"
1 2 3 4 5 6 7 8 9 10 from django.contrib import adminfrom django.urls import pathfrom . import viewsurlpatterns = [ path('admin/' , admin.site.urls), path('' , views.index), path('alipay_return/' , views.alipay_return) ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import randomimport stringfrom django.http import HttpResponseRedirectfrom django.shortcuts import renderfrom ali_django.alipay import alipay_pay, create_alipayfrom django.conf import settingsdef index (request ): if request.method == "GET" : return render(request, 'index.html' ) elif request.method == "POST" : out_trade_no = "" .join(random.sample(string.ascii_letters + string.digits, 32 )) order_string = alipay_pay(subject="测试商品" , total_amount=100 , out_trade_no=out_trade_no,return_url_view='alipay_return' ) return HttpResponseRedirect(settings.GATEWAY + order_string) def alipay_return (request ): processed_dict = {} for key, value in request.GET.items(): processed_dict[key] = value sign = processed_dict.pop("sign" , None ) new_alipay = create_alipay() verify_re = new_alipay.verify(processed_dict, sign) if verify_re is True : print ("支付成功" ) else : print ("支付失败" )
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 支付宝支付接口测试</title > </head > <body > <form action ="" method ="post" > <input type ="submit" value ="提交" > </form > </body > </html >