使用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(订单标题),所以先构造订单接口,生成订单:
1 class OrderSnHandler(BaseHandler): 2 @authenticated 3 async def post(self, *args, **kwargs): 4 """ 5 创建订单信息 6 :param request: 7 :return: 8 """ 9 res_data = {} 10 req_data = self.request.body.decode("utf8") 11 req_data = json.loads(req_data) 12 post_script = req_data.get("post_script") 13 order_form = TradeOrderSnForm.from_json(req_data) 14 if order_form.validate(): 15 try: 16 order_mount = order_form.order_mount.data 17 orders_object = await self.application.objects.create( 18 OrderInfo, 19 pay_status=OrderInfo.ORDER_STATUS[4][0], 20 pay_time=datetime.now(), 21 order_sn=OrderInfo.generate_order_sn(), 22 user=self.current_user, 23 order_mount=order_mount, 24 post_script=post_script 25 ) 26 res_data["id"] = orders_object.id 27 except Exception: 28 self.set_status(400) 29 res_data["content"] = "订单创建失败" 30 else: 31 res_data["content"] = order_form.errors 32 33 self.finish(res_data)
4. 构造支付接口
(1) 构造支付接口类
流程:RSA导入公钥和私钥-->构造请求参数biz_content-->构造支付宝公共请求参数-->排序并拼接参数为规范字符串-->生成签名后的字符串-->请求支付宝接口-->对支付宝接口返回的数据进行签名比对
1 class AliPay(object): 2 """ 3 支付宝支付接口 4 """ 5 6 def __init__(self, appid, app_notify_url, app_private_key_path, 7 alipay_public_key_path, return_url, debug=False): 8 self.appid = appid 9 self.app_notify_url = app_notify_url 10 self.app_private_key_path = app_private_key_path 11 self.app_private_key = None 12 self.return_url = return_url 13 with open(self.app_private_key_path) as fp: 14 self.app_private_key = RSA.importKey(fp.read()) 15 16 self.alipay_public_key_path = alipay_public_key_path 17 with open(self.alipay_public_key_path) as fp: 18 self.alipay_public_key = RSA.import_key(fp.read()) 19 20 if debug is True: 21 self.__gateway = "https://openapi.alipaydev.com/gateway.do" 22 else: 23 self.__gateway = "https://openapi.alipay.com/gateway.do" 24 25 def direct_pay(self, subject, out_trade_no, total_amount, **kwargs): # NOQA 26 """ 27 构造请求参数biz_content, 28 并将其放入公共请求参数中, 29 返回签名sign的data 30 :param subject: 31 :param out_trade_no: 32 :param total_amount: 33 :param kwargs: 34 :return: 35 """ 36 biz_content = { 37 "subject": subject, 38 "out_trade_no": out_trade_no, 39 "total_amount": total_amount, 40 "product_code": "FAST_INSTANT_TRADE_PAY", 41 } 42 43 biz_content.update(kwargs) 44 data = self.build_body( 45 "alipay.trade.page.pay", 46 biz_content, 47 self.return_url 48 ) 49 return self.sign_data(data) 50 51 def build_body(self, method, biz_content, return_url=None): 52 """ 53 构造公共请求参数 54 :param method: 55 :param biz_content: 56 :param return_url: 57 :return: 58 """ 59 data = { 60 "app_id": self.appid, 61 "method": method, 62 "charset": "utf-8", 63 "sign_type": "RSA2", 64 "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 65 "version": "1.0", 66 "biz_content": biz_content 67 } 68 69 if return_url: 70 data["notify_url"] = self.app_notify_url 71 data["return_url"] = self.return_url 72 73 return data 74 75 def sign_data(self, data): 76 """ 77 拼接排序后的data,以&连接成符合规范的字符串,并对字符串签名, 78 将签名后的字符串通过quote_plus格式化, 79 将请求参数中的url格式化为safe的,获得最终的订单信息字符串 80 :param data: 81 :return: 82 """ 83 # 签名中不能有sign字段 84 if "sign" in data: 85 data.pop("sign") 86 87 unsigned_items = self.ordered_data(data) 88 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) 89 sign = self.sign_string(unsigned_string.encode("utf-8")) 90 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) 91 92 signed_string = quoted_string + "&sign=" + quote_plus(sign) 93 return signed_string 94 95 def ordered_data(self, data): 96 """ 97 将请求参数字典排序, 98 支付宝接口要求是拼接的有序参数字符串 99 :param data: 100 :return: 101 """ 102 complex_keys = [] 103 for key, value in data.items(): 104 if isinstance(value, dict): 105 complex_keys.append(key) 106 107 for key in complex_keys: 108 data[key] = json.dumps(data[key], separators=(',', ':')) 109 110 return sorted([(k, v) for k, v in data.items()]) 111 112 def sign_string(self, unsigned_string): 113 """ 114 生成签名,并进行base64 编码, 115 转换为unicode表示并去掉换行符 116 :param unsigned_string: 117 :return: 118 """ 119 key = self.app_private_key 120 signer = PKCS1_v1_5.new(key) 121 signature = signer.sign(SHA256.new(unsigned_string)) 122 sign = encodebytes(signature).decode("utf8").replace("\n", "") 123 return sign 124 125 def _verify(self, raw_content, signature): 126 """ 127 对支付宝接口返回的数据进行签名比对, 128 验证是否来源于支付宝 129 :param raw_content: 130 :param signature: 131 :return: 132 """ 133 key = self.alipay_public_key 134 signer = PKCS1_v1_5.new(key) 135 digest = SHA256.new() 136 digest.update(raw_content.encode("utf8")) 137 if signer.verify(digest, decodebytes(signature.encode("utf8"))): 138 return True 139 return False 140 141 def verify(self, data, signature): 142 """ 143 验证支付宝返回的数据,防止是伪造信息 144 :param data: 145 :param signature: 146 :return: 147 """ 148 if "sign_type" in data: 149 data.pop("sign_type") 150 unsigned_items = self.ordered_data(data) 151 message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) 152 return self._verify(message, signature)
(2) 构造支付链接接口
通过步骤3创建的订单信息生成支付链接,这里接口我采用协程+异步的方式,authenticated是自定义的JWT验证装饰器,private_key_path和ali_pub_key_path是前面生成的应用私钥和支付宝公钥文件地址
1 class GenPayLinkHandler(BaseHandler): 2 @authenticated 3 async def get(self, *args, **kwargs): 4 """ 5 通过订单生成支付链接 6 :param args: 7 :param kwargs: 8 :return: 9 """ 10 res_data = {} 11 order_id = get_int_or_none(self.get_argument("id", None)) 12 if not order_id: 13 self.set_status(400) 14 self.write({"content": "缺少order_id参数"}) 15 16 try: 17 order_obj = await self.application.objects.get( 18 OrderInfo, id=order_id, 19 pay_status=OrderInfo.ORDER_STATUS[4][0] 20 ) 21 out_trade_no = order_obj.order_sn 22 order_mount = order_obj.order_mount 23 subject = order_obj.post_script 24 alipay = AliPay( 25 appid=settings["ALI_APPID"], 26 app_notify_url="{}/alipay/return/".format(settings["SITE_URL"]), 27 app_private_key_path=settings["private_key_path"], 28 alipay_public_key_path=settings["ali_pub_key_path"], 29 debug=True, 30 return_url="{}/alipay/return/".format(settings["SITE_URL"]) 31 ) 32 url = alipay.direct_pay( 33 subject=subject, 34 out_trade_no=out_trade_no, 35 total_amount=order_mount, 36 return_url="{}/alipay/return/".format(settings["SITE_URL"]) 37 ) 38 re_url = settings["RETURN_URI"].format(data=url) 39 res_data["re_url"] = re_url 40 except OrderInfo.DoesNotExist: 41 self.set_status(400) 42 res_data["content"] = "订单不存在" 43 44 self.finish(res_data)
返回结果:
打开支付链接可以看到:
(3) 构造支付的回调接口
在支付完成后,支付宝会调用在开发者信息中配置的回调url,通过GET方法回调return_ul,通过POST方法发送notify主动通知商户返回服务器里指定的页面,这里分别实现return_ul和notify_url对应的接口,支付宝返回的notify_url是个异步的所以我这里也以异步的方式实现这个接口:
1 class AlipayHandler(BaseHandler): 2 def get(self, *args, **kwargs): 3 """ 4 处理支付宝的return_url返回 5 :param request: 6 :return: 7 """ 8 res_data = {} 9 processed_dict = {} 10 req_data = self.request.arguments 11 req_data = format_arguments(req_data) 12 for key, value in req_data.items(): 13 processed_dict[key] = value[0] 14 15 sign = processed_dict.pop("sign", None) 16 alipay = AliPay( 17 appid=settings["ALI_APPID"], 18 app_notify_url="{}/alipay/return/".format(settings["SITE_URL"]), 19 app_private_key_path=settings["private_key_path"], 20 alipay_public_key_path=settings["ali_pub_key_path"], 21 debug=True, 22 return_url="{}/alipay/return/".format(settings["SITE_URL"]) 23 ) 24 25 verify_re = alipay.verify(processed_dict, sign) 26 27 if verify_re is True: 28 res_data["content"] = "success" 29 else: 30 res_data["content"] = "Failed" 31 32 self.finish(res_data) 33 34 async def post(self, *args, **kwargs): 35 """ 36 处理支付宝的notify_url 37 :param request: 38 :return: 39 """ 40 processed_dict = {} 41 req_data = self.request.body_arguments 42 req_data = format_arguments(req_data) 43 for key, value in req_data.items(): 44 processed_dict[key] = value[0] 45 46 sign = processed_dict.pop("sign", None) 47 alipay = AliPay( 48 appid=settings["ALI_APPID"], 49 app_notify_url="{}/alipay/return/".format(settings["SITE_URL"]), 50 app_private_key_path=settings["private_key_path"], 51 alipay_public_key_path=settings["ali_pub_key_path"], 52 debug=True, 53 return_url="{}/alipay/return/".format(settings["SITE_URL"]) 54 ) 55 56 verify_re = alipay.verify(processed_dict, sign) 57 58 if verify_re is True: 59 order_sn = processed_dict.get('out_trade_no') 60 trade_no = processed_dict.get('trade_no') 61 trade_status = processed_dict.get('trade_status') 62 63 orders_query = OrderInfo.update( 64 pay_status=trade_status, 65 trade_no=trade_no, 66 pay_time=datetime.now() 67 ).where( 68 OrderInfo.order_sn == order_sn 69 ) 70 await self.application.objects.execute( 71 orders_query 72 ) 73 74 self.finish("success")
测试支付结果:
- Android开发中的第三方支付SDK的接入与使用(支付宝+微信支付+银联)
- 关于android webview 端调原生app 的支付宝接口实现 还有BeeCloud(秒支付) 的接入使用
- IOS 第三方支付的使用:支付宝
- Android 支付宝支付、微信支付、银联支付 整合第三方支付接入方法(后台订单支付API设计)
- 支付宝微信第三方支付使用流程
- Android 支付宝支付、微信支付、银联支付 整合第三方支付接入方法
- IOS 第三方支付的使用:支付宝
- laravel5.4框架微信,支付宝等支付和退款使用第三方插件Omnipay
- 使用Python接入银联支付和支付宝支付的实现
- 第三方支付:微信公众号接入支付宝支付开发
- android 第三方支付之支付宝使用详解
- Android 支付宝支付、微信支付、银联支付 整合第三方支付接入方法(后台订单支付API设计)
- Java第三方支付接入案例(支付宝)
- android第三方支付,支付宝使用学习
- 支付宝移动支付,服务端对异步通知信息验签的时候验签失败,支付宝pc端支付接入PHP实现
- 支付宝手机网站接入2-支付结果异步通知
- <android> 第三方支付sdk接入 支付宝、微信支付
- 接入第三方支付_支付宝
- laravel框架下的pc支付宝支付接入
- 连连支付的接入和简单使用