使用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")

測試支付結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM