使用Tornado异步接入第三方(支付宝)支付
目前国内比较流行的第三方支付主要有支付宝和微信支付,博主最近研究了下如何用Python接入支付宝支付,这里我以Tornado作为web框架,接入支付宝构造支付接口。
使用Tornado异步接入支付宝支付流程:
1. 进入蚂蚁金服开放平台填写开发者信息、应用信息
2. 配置RSA256密钥,生成支付宝和应用的密钥
3. 构造订单接口API,生成订单
4. 构造支付接口
1. 进入蚂蚁金服开放平台填写开发者信息、应用信息
这里通过沙箱环境开发测试接口,蚂蚁金服开放平台-->开发者中心-->研发者服务-->沙箱应用,配置沙箱应用信息:
设置授权回调地址,注意:这个地址一定要是外网IP地址(我这里是我的阿里云服务器地址),回调地址是自己支付完回调的api地址,可通过扫码下载沙箱板支付宝钱包进行支付测试:
设置沙箱账号,设置买家和买家的测试账号,支付宝会默认给买家账户99999元,可用来测试支付接口是否成功:
2. 配置RSA256密钥,生成支付宝和应用的密钥
支付宝默认有两种加密算法生成密钥:RSA(SHA1)和RSA2(SHA256),鉴于安全性支付宝推荐使用RSA2(SHA256)密钥。通过查看密钥生成文档https://docs.open.alipay.com/291/105971得知密钥生成方法,按文档提示下载密钥生成工具,解压后打开生成工具,选择密码格式(Python当然就是选择PKCS1了)和密码长度,生成公钥和私钥:
生成后可在RSA密钥文件夹下查看应用的公钥和私钥,并将应用公钥上传到开放平台的开发者环境中:
3. 构造订单接口API,生成订单
查看支付接口文档:https://docs.open.alipay.com/270/alipay.trade.page.pay/可知:
支付接口的必填参数有out_trade_no(订单号)、total_amount(订单金额)、subject(订单标题),所以先构造订单接口,生成订单:
class OrderSnHandler(BaseHandler):
@authenticated
async def post(self, *args, **kwargs):
"""
创建订单信息
:param request:
:return:
"""
res_data = {}
req_data = self.request.body.decode("utf8")
req_data = json.loads(req_data)
post_script = req_data.get("post_script")
order_form = TradeOrderSnForm.from_json(req_data)
if order_form.validate():
try:
order_mount = order_form.order_mount.data
orders_object = await self.application.objects.create(
OrderInfo,
pay_status=OrderInfo.ORDER_STATUS[4][0],
pay_time=datetime.now(),
order_sn=OrderInfo.generate_order_sn(),
user=self.current_user,
order_mount=order_mount,
post_script=post_script
)
res_data["id"] = orders_object.id
except Exception:
self.set_status(400)
res_data["content"] = "订单创建失败"
else:
res_data["content"] = order_form.errors self.finish(res_data)
4. 构造支付接口
(1) 构造支付接口类
流程:RSA导入公钥和私钥-->构造请求参数biz_content-->构造支付宝公共请求参数-->排序并拼接参数为规范字符串-->生成签名后的字符串-->请求支付宝接口-->对支付宝接口返回的数据进行签名比对
class AliPay(object):
"""
支付宝支付接口
""" def __init__(self, appid, app_notify_url, app_private_key_path,
alipay_public_key_path, return_url, debug=False):
self.appid = appid
self.app_notify_url = app_notify_url
self.app_private_key_path = app_private_key_path
self.app_private_key = None
self.return_url = return_url
with open(self.app_private_key_path) as fp:
self.app_private_key = RSA.importKey(fp.read()) self.alipay_public_key_path = alipay_public_key_path
with open(self.alipay_public_key_path) as fp:
self.alipay_public_key = RSA.import_key(fp.read()) if debug is True:
self.__gateway = "https://openapi.alipaydev.com/gateway.do"
else:
self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, **kwargs): # NOQA
"""
构造请求参数biz_content,
并将其放入公共请求参数中,
返回签名sign的data
:param subject:
:param out_trade_no:
:param total_amount:
:param kwargs:
:return:
"""
biz_content = {
"subject": subject,
"out_trade_no": out_trade_no,
"total_amount": total_amount,
"product_code": "FAST_INSTANT_TRADE_PAY",
} biz_content.update(kwargs)
data = self.build_body(
"alipay.trade.page.pay",
biz_content,
self.return_url
)
return self.sign_data(data) def build_body(self, method, biz_content, return_url=None):
"""
构造公共请求参数
:param method:
:param biz_content:
:param return_url:
:return:
"""
data = {
"app_id": self.appid,
"method": method,
"charset": "utf-8",
"sign_type": "RSA2",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"biz_content": biz_content
} if return_url:
data["notify_url"] = self.app_notify_url
data["return_url"] = self.return_url return data def sign_data(self, data):
"""
拼接排序后的data,以&连接成符合规范的字符串,并对字符串签名,
将签名后的字符串通过quote_plus格式化,
将请求参数中的url格式化为safe的,获得最终的订单信息字符串
:param data:
:return:
"""
# 签名中不能有sign字段
if "sign" in data:
data.pop("sign") unsigned_items = self.ordered_data(data)
unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
sign = self.sign_string(unsigned_string.encode("utf-8"))
quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) signed_string = quoted_string + "&sign=" + quote_plus(sign)
return signed_string def ordered_data(self, data):
"""
将请求参数字典排序,
支付宝接口要求是拼接的有序参数字符串
:param data:
:return:
"""
complex_keys = []
for key, value in data.items():
if isinstance(value, dict):
complex_keys.append(key) for key in complex_keys:
data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign_string(self, unsigned_string):
"""
生成签名,并进行base64 编码,
转换为unicode表示并去掉换行符
:param unsigned_string:
:return:
"""
key = self.app_private_key
signer = PKCS1_v1_5.new(key)
signature = signer.sign(SHA256.new(unsigned_string))
sign = encodebytes(signature).decode("utf8").replace("\n", "")
return sign def _verify(self, raw_content, signature):
"""
对支付宝接口返回的数据进行签名比对,
验证是否来源于支付宝
:param raw_content:
:param signature:
:return:
"""
key = self.alipay_public_key
signer = PKCS1_v1_5.new(key)
digest = SHA256.new()
digest.update(raw_content.encode("utf8"))
if signer.verify(digest, decodebytes(signature.encode("utf8"))):
return True
return False def verify(self, data, signature):
"""
验证支付宝返回的数据,防止是伪造信息
:param data:
:param signature:
:return:
"""
if "sign_type" in data:
data.pop("sign_type")
unsigned_items = self.ordered_data(data)
message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
return self._verify(message, signature)
(2) 构造支付链接接口
通过步骤3创建的订单信息生成支付链接,这里接口我采用协程+异步的方式,authenticated是自定义的JWT验证装饰器,private_key_path和ali_pub_key_path是前面生成的应用私钥和支付宝公钥文件地址
class GenPayLinkHandler(BaseHandler):
@authenticated
async def get(self, *args, **kwargs):
"""
通过订单生成支付链接
:param args:
:param kwargs:
:return:
"""
res_data = {}
order_id = get_int_or_none(self.get_argument("id", None))
if not order_id:
self.set_status(400)
self.write({"content": "缺少order_id参数"}) try:
order_obj = await self.application.objects.get(
OrderInfo, id=order_id,
pay_status=OrderInfo.ORDER_STATUS[4][0]
)
out_trade_no = order_obj.order_sn
order_mount = order_obj.order_mount
subject = order_obj.post_script
alipay = AliPay(
appid=settings["ALI_APPID"],
app_notify_url="{}/alipay/return/".format(settings["SITE_URL"]),
app_private_key_path=settings["private_key_path"],
alipay_public_key_path=settings["ali_pub_key_path"],
debug=True,
return_url="{}/alipay/return/".format(settings["SITE_URL"])
)
url = alipay.direct_pay(
subject=subject,
out_trade_no=out_trade_no,
total_amount=order_mount,
return_url="{}/alipay/return/".format(settings["SITE_URL"])
)
re_url = settings["RETURN_URI"].format(data=url)
res_data["re_url"] = re_url
except OrderInfo.DoesNotExist:
self.set_status(400)
res_data["content"] = "订单不存在" self.finish(res_data)
返回结果:
打开支付链接可以看到:
(3) 构造支付的回调接口
在支付完成后,支付宝会调用在开发者信息中配置的回调url,通过GET方法回调return_ul,通过POST方法发送notify主动通知商户返回服务器里指定的页面,这里分别实现return_ul和notify_url对应的接口,支付宝返回的notify_url是个异步的所以我这里也以异步的方式实现这个接口:
class AlipayHandler(BaseHandler):
def get(self, *args, **kwargs):
"""
处理支付宝的return_url返回
:param request:
:return:
"""
res_data = {}
processed_dict = {}
req_data = self.request.arguments
req_data = format_arguments(req_data)
for key, value in req_data.items():
processed_dict[key] = value[0] sign = processed_dict.pop("sign", None)
alipay = AliPay(
appid=settings["ALI_APPID"],
app_notify_url="{}/alipay/return/".format(settings["SITE_URL"]),
app_private_key_path=settings["private_key_path"],
alipay_public_key_path=settings["ali_pub_key_path"],
debug=True,
return_url="{}/alipay/return/".format(settings["SITE_URL"])
) verify_re = alipay.verify(processed_dict, sign) if verify_re is True:
res_data["content"] = "success"
else:
res_data["content"] = "Failed" self.finish(res_data) async def post(self, *args, **kwargs):
"""
处理支付宝的notify_url
:param request:
:return:
"""
processed_dict = {}
req_data = self.request.body_arguments
req_data = format_arguments(req_data)
for key, value in req_data.items():
processed_dict[key] = value[0] sign = processed_dict.pop("sign", None)
alipay = AliPay(
appid=settings["ALI_APPID"],
app_notify_url="{}/alipay/return/".format(settings["SITE_URL"]),
app_private_key_path=settings["private_key_path"],
alipay_public_key_path=settings["ali_pub_key_path"],
debug=True,
return_url="{}/alipay/return/".format(settings["SITE_URL"])
) verify_re = alipay.verify(processed_dict, sign) if verify_re is True:
order_sn = processed_dict.get('out_trade_no')
trade_no = processed_dict.get('trade_no')
trade_status = processed_dict.get('trade_status') orders_query = OrderInfo.update(
pay_status=trade_status,
trade_no=trade_no,
pay_time=datetime.now()
).where(
OrderInfo.order_sn == order_sn
)
await self.application.objects.execute(
orders_query
) self.finish("success")
测试支付结果:
使用Tornado异步接入第三方(支付宝)支付的更多相关文章
- 商家 APP 如何接入新版支付宝支付,老版本商家如何升级
代码地址如下:http://www.demodashi.com/demo/14006.html 前言 支付宝移动支付2.0版本对比1.0版本做了较大更新,新申请的商家都需要采用最新2.0版本 SDK ...
- PC、h5项目接入第三方支付宝扫码登录、扫码付款
首先介绍一下pc项目接入支付宝扫码支付. 1.pc.移动接入支付宝扫码支付. 其实这个逻辑很简单,前端所需要处理的不是很多,后台会给一个连接,前端只需要将要支付的订单id拼接在这个连接上,然后打开跳转 ...
- asp.net mvc 接入最新支付宝支付+退款 alipay-sdk-NET-20170615110549
第1步: https://openhome.alipay.com/developmentDocument.htm 第2步:下载sdk和demo https://docs.open.alipay.com ...
- cocos2d-x android工程接入第三方支付宝SDK
1. 首先去支付宝官网下载开发者文档 2. 然后按着开发者文档将支付宝的sdk导入到你的工程中,并关联到工程中,步骤入下图: (1)将从支付宝官方网站获得的支付宝的sdk的jar包拷贝到工程中的lib ...
- cocos2d-x C++ iOS工程集成第三方支付宝支付功能
一.在支付宝开放平台下载支付宝SDK(https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.WWgVz8&tr ...
- Java 支付宝支付,退款,单笔转账到支付宝账户(支付宝支付)
最近一直在接触第三方,刚接入完支付宝的API做一下总结,个人能力薄弱有不对的地方望指教. 做的是一个小型电商项目,所以会接入第三方的支付和登入功能, 第一次接入第三方撸了很多官方文档. 进入主题, ...
- (转载)Android支付宝支付封装代码
Android支付宝支付封装代码 投稿:lijiao 字体:[增加 减小] 类型:转载 时间:2015-12-22我要评论 这篇文章主要介绍了Android支付宝支付封装代码,Android支付的时候 ...
- APP支付宝支付接入
1.app支付简介 买家可以在手机,掌上电脑等无线设备的应用程序内,通过支付宝(支付宝app或网页版支付宝)付款购买商品,且资金实行实时到账. 2.申请条件 1.申请前必须拥有经过实名认证的支付宝账户 ...
- PHP接入支付宝支付
创建应用 使用支付宝账号登录开放平台创建应用,应用创建成功之后可以得到APPID等相关信息 接着需要设置RSA密钥,可以使用蚂蚁金服开放平台提供的生成工具,生成完密钥需在开放平台中填写. 代码接入 引 ...
随机推荐
- vue引入fastclick设置输入框type="number"报错Failed to execute 'setSelectionRange' on 'HTMLInputElement': The input element's type ('number') does not support selection.的解决办法
将输入框type设为text,通过正则验证输入的值
- 《C#手札》--基础知识
第一记 C#基本语法: 语言分隔符: 分号 (;) :语句的分割,表示一句话结束: 花括号 ({}):表示一个代码号,是一个整体,花括号要成对使用: 方括号 ([]): 定义数组和访问数组元素时使用: ...
- 关于MarkDown里的图片问题
网上看了很多人为了得到那串URL,需要弄什么python,还有自己弄个服务器. 在我看来这些都是多此一举,只要有个GitHub,然后再开两个页面,一个页面写readme,另一个写issue(不是真的写 ...
- springboot自定义配置信息读取
在properties配置文件加入自定义配置例如: zxgl.detail.url=http://*****/zxgl-web/news/viewNewsIndexDetail.do?id= #资讯t ...
- springboot格式化时间
使用@RestController注解,返回的java对象中若含有date类型的属性,则默认输出为TIMESTAMP时间戳格式,可以在配置文件加入下面配置 spring.jackson.date-fo ...
- 使用Github时遇到问题的解决方法
记录了一些我在使用Github时遇到问题的解决方法 git中报unable to auto-detect email address 错误的解决办法 问题描述: 执行 git commit -m &q ...
- 从tom大叔那想着拿书的,呵呵。
//var tgtttime = new Date("2014/05/26 09:59:30"); var tgtttime = new Date("2014/05/26 ...
- LeetCode第十九题-链表节点的删除
Remove Nth Node From End of List 问题简介;给定链表,从链表末尾删除第n个节点并返回其头部 例: 给定链表:1-> 2-> 3-> 4-> 5, ...
- 【Selenium】各浏览器(firefox,chrome,ie)驱动下载地址汇总
前两天使用Selenium分布式时,总抛出异常.更新成最新驱动可以解决.其中chrome异常如下, "platform": "WINDOWS" File &qu ...
- UltraEdit注册机 及使用方法详解
转载自:http://www.iyaxi.com/ultraedit-key/ UltraEdit是一款强大的文字编辑器,很多编程的.搞设计的等等都能用到它,具体功能请自行百度.今天为大家带来UE软件 ...