Django rest framework之支付功能
一.支付宝支付
1.进入蚂蚁金服开放平台(查看api):
1.1在正式生产环境中需要创建应用(需审核):
1.2沙箱环境(测试):
可以在文档中查看对应的api接口
对应接口参数,sign_type加密算法推荐RSA2更安全
sign签名中生成RSA密钥,下载生成密钥后会生成两个文件(公钥和私钥文件)
将公钥填写在在RSA
在生成的公钥和私钥文件头和尾加上(-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----)
取下支付宝公钥放在文件中(用于查询·订单状态)
共有三个文件(公钥,私钥,支付宝公钥)
支付接口部分参数
拼接参数并签名赋值给sign
2.付款接口代码(依赖pycryptodome包):
2.1会生成支付宝返回的url,并解读,可支付(测试)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 # -*- coding: utf-8 -*- 2 __author__ = 'LYQ' 3 # pip install pycryptodome,需要安装 4 5 from datetime import datetime 6 from Crypto.PublicKey import RSA 7 from Crypto.Signature import PKCS1_v1_5 8 from Crypto.Hash import SHA256 9 from base64 import b64encode, b64decode 10 from urllib.parse import quote_plus 11 from urllib.parse import urlparse, parse_qs 12 from urllib.request import urlopen 13 from base64 import decodebytes, encodebytes 14 15 import json 16 17 18 class AliPay(object): 19 """ 20 支付宝支付接口 21 """ 22 23 def __init__(self, appid, app_notify_url, app_private_key_path, 24 alipay_public_key_path, return_url, debug=False): 25 ''' 26 初始化参数appid,app_notify_url,app_private_key_path(这是私钥文件的路径), 27 app_private_key初始化为None,打开私钥文件用Crypto.PublicKey中的RSA.importKey()对私钥加密 28 alipay_public_key用同样方法加密 29 ''' 30 self.appid = appid 31 self.app_notify_url = app_notify_url 32 self.app_private_key_path = app_private_key_path 33 self.app_private_key = None 34 self.return_url = return_url 35 with open(self.app_private_key_path) as fp: 36 self.app_private_key = RSA.importKey(fp.read()) 37 38 self.alipay_public_key_path = alipay_public_key_path 39 with open(self.alipay_public_key_path) as fp: 40 self.alipay_public_key = RSA.import_key(fp.read()) 41 42 if debug is True: 43 self.__gateway = "https://openapi.alipaydev.com/gateway.do" 44 else: 45 self.__gateway = "https://openapi.alipay.com/gateway.do" 46 47 def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): 48 #应用请求参数,对应文档字段(有四个参数必填) 49 biz_content = { 50 "subject": subject, 51 "out_trade_no": out_trade_no, 52 "total_amount": total_amount, 53 "product_code": "FAST_INSTANT_TRADE_PAY", 54 # "qr_pay_mode":4 55 } 56 #对传入的参数更新biz_content 57 biz_content.update(kwargs) 58 data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) 59 return self.sign_data(data) 60 61 def build_body(self, method, biz_content, return_url=None): 62 #生成主要参数,与biz_content同级,嵌套 63 data = { 64 "app_id": self.appid, 65 "method": method, 66 "charset": "utf-8", 67 "sign_type": "RSA2", 68 "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 69 "version": "1.0", 70 "biz_content": biz_content 71 } 72 73 if return_url is not None: 74 #还是公共请求参数 75 data["notify_url"] = self.app_notify_url 76 data["return_url"] = self.return_url 77 78 return data 79 80 def sign_data(self, data): 81 #签名时不能有sign字段,去掉sign字段 82 data.pop("sign", None) 83 # 参数需要排序,排序后的字符串,数组里面嵌套元组"[(),()...]" 84 unsigned_items = self.ordered_data(data) 85 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) 86 sign = self.sign(unsigned_string.encode("utf-8")) 87 # ordered_items = self.ordered_data(data) 88 #quote_plus()对url处理 89 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) 90 91 # 获得最终的订单信息字符串 92 signed_string = quoted_string + "&sign=" + quote_plus(sign) 93 return signed_string 94 95 def ordered_data(self, data): 96 complex_keys = [] 97 for key, value in data.items(): 98 if isinstance(value, dict): 99 complex_keys.append(key) 100 101 # 将字典类型的数据dump出来 102 for key in complex_keys: 103 data[key] = json.dumps(data[key], separators=(',', ':')) 104 105 return sorted([(k, v) for k, v in data.items()]) 106 107 def sign(self, unsigned_string): 108 # 开始计算签名 109 key = self.app_private_key 110 signer = PKCS1_v1_5.new(key) 111 #调用算法生成签名 112 signature = signer.sign(SHA256.new(unsigned_string)) 113 # 进行base64 编码,转换为unicode表示并移除回车 114 sign = encodebytes(signature).decode("utf8").replace("\n", "") 115 return sign 116 117 def _verify(self, raw_content, signature): 118 # 开始计算签名 119 key = self.alipay_public_key 120 signer = PKCS1_v1_5.new(key) 121 digest = SHA256.new() 122 digest.update(raw_content.encode("utf8")) 123 if signer.verify(digest, decodebytes(signature.encode("utf8"))): 124 return True 125 return False 126 127 def verify(self, data, signature): 128 if "sign_type" in data: 129 sign_type = data.pop("sign_type") 130 # 排序后的字符串 131 unsigned_items = self.ordered_data(data) 132 message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) 133 return self._verify(message, signature) 134 135 136 if __name__ == "__main__": 137 return_url = 'http://127.0.0.1:8000/?total_amount=100.00×tamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2FvhdDYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdpVQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&method=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0' 138 o = urlparse(return_url) 139 query = parse_qs(o.query) 140 processed_query = {} 141 ali_sign = query.pop("sign")[0] 142 143 alipay = AliPay( 144 # 沙箱环境appid 145 appid="2016091800536621", 146 app_notify_url="http://47.106.211.59:8008/alipay/return/", 147 # 私钥路径 148 app_private_key_path="../trade/keys/private_2048.txt", 149 alipay_public_key_path="../trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 150 debug=True, # 默认False, 151 # 支付成功后跳转的url 152 return_url="http://47.106.211.59:8008/alipay/return/" 153 ) 154 155 # for key, value in query.items(): 156 # processed_query[key] = value[0] 157 # print(alipay.verify(processed_query, ali_sign)) 158 159 url = alipay.direct_pay( 160 subject="测试订单4", 161 out_trade_no="201667201", 162 total_amount=100, 163 return_url="http://47.106.211.59:8008/alipay/return/" 164 ) 165 re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) 166 167 print(re_url)
2.2访问url效果如下:可用沙箱支付,支付成功会跳转到如图的return_url(同步),notify_url会发送异步请求
3.支付宝通知接口验证:
3.1页面回跳参数:
3.2接收查看:
3.2.1返回参数查看
大概内容如上,是一个字典
3.2.2验证代码如下,verify()函数如上,返回False:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 if __name__ == "__main__": 2 #返回的url 3 return_url = 'http://127.0.0.1:8000/?total_amount=100.00×tamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2FvhdDYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdpVQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&method=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0' 4 o = urlparse(return_url) 5 query = parse_qs(o.query) 6 processed_query = {} 7 #把sign给pop出 8 ali_sign = query.pop("sign")[0] 9 10 alipay = AliPay( 11 # 沙箱环境appid 12 appid="2016091800536621", 13 app_notify_url="http://47.106.211.59:8008/alipay/return/", 14 # 私钥路径 15 app_private_key_path="../trade/keys/private_2048.txt", 16 alipay_public_key_path="../trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 17 debug=True, # 默认False, 18 # 支付成功后跳转的url 19 return_url="http://47.106.211.59:8008/alipay/return/" 20 ) 21 22 for key, value in query.items(): 23 processed_query[key] = value[0] 24 #调用verify对信息对比,一样就返回True,否则返回False 25 print(alipay.verify(processed_query, ali_sign))
4.django集成支付宝notify_url和return_url接口:
return_url为同步
notify_url为异步
共有四种状态
数据库model对应代码部分:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 class OrderInfo(models.Model): 2 """ 3 订单 4 """ 5 #对应四种状态 6 ORDER_STATUS = ( 7 ("TRADE_SUCCESS", "成功"), 8 ("TRADE_CLOSED", "超时关闭"), 9 ("WAIT_BUYER_PAY", "交易创建"), 10 ("TRADE_FINISHED", "交易结束"), 11 ("paying", "待支付"), 12 ) 13 ......
notify_url返回的业务参数
处理return_url和notify_url的接口:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 class AlipayView(APIView): 2 ''' 3 支付宝接口 4 ''' 5 6 def get(self, request): 7 ''' 8 处理return_url返回 9 :param request: 10 :return: 11 ''' 12 process_dic = {} 13 #把POST中的数据全取出来 14 for key, value in request.GET.items(): 15 process_dic[key] = value 16 #把sign去掉 17 sign = process_dic.pop('sign', None) 18 alipay = AliPay( 19 # 沙箱环境appid 20 appid="2016091800536621", 21 app_notify_url="http://47.106.211.59:8008/alipay/return/", 22 # 私钥路径 23 app_private_key_path=private_key_path, 24 alipay_public_key_path=ali_pub_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 25 debug=True, # 默认False, 26 return_url="http://47.106.211.59:8008/alipay/return/" 27 ) 28 #验证 29 verify_re = alipay.verify(process_dic, sign) 30 if verify_re is True: 31 order_sn = process_dic.get('order_sn', None) 32 trade_no = process_dic.get('trade_no', None) 33 trade_status = process_dic.get('trade_status', None) 34 exsisted_orders = OrderInfo.objects.filter(order_sn=order_sn) 35 for exsisted_order in exsisted_orders: 36 #销量数目修改 37 order_goods=exsisted_order.objects.all() 38 for order_good in order_goods: 39 goods=order_good.goods() 40 goods.sold_num+=order_good.goods_num 41 goods.save() 42 exsisted_order.pay_status = trade_status 43 exsisted_order.trade_no = trade_no 44 exsisted_order.pay_time = datetime.now() 45 exsisted_order.save() 46 response = redirect("index") 47 response.set_cookie("nextPath","pay", max_age=3) 48 return response 49 else: 50 response = redirect("index") 51 return response 52 53 def post(self, request): 54 ''' 55 处理notify_url,会把返回的所有参数放在post中 56 :param request: 57 :return: 58 ''' 59 process_dic = {} 60 for key, value in request.POST.items(): 61 process_dic[key] = value 62 sign = process_dic.pop('sign', None) 63 alipay = AliPay( 64 # 沙箱环境appid 65 appid="2016091800536621", 66 app_notify_url="http://47.106.211.59:8008/alipay/return/", 67 # 私钥路径 68 app_private_key_path=private_key_path, 69 alipay_public_key_path=ali_pub_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 70 debug=True, # 默认False, 71 return_url="http://47.106.211.59:8008/alipay/return/" 72 ) 73 74 verify_re = alipay.verify(process_dic, sign) 75 if verify_re is True: 76 #获取业务参数 77 order_sn = process_dic.get('order_sn', None) 78 trade_no = process_dic.get('trade_no', None) 79 trade_status = process_dic.get('trade_status', None) 80 exsisted_orders = OrderInfo.objects.filter(order_sn=order_sn) 81 #保存到数据库 82 for exsisted_order in exsisted_orders: 83 exsisted_order.pay_status = trade_status 84 exsisted_order.trade_no = trade_no 85 exsisted_order.pay_time = datetime.now() 86 exsisted_order.save() 87 return Response("success")
订单序列化:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 class OrderSerializer(serializers.ModelSerializer): 2 ''' 3 订单序列化 4 ''' 5 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) 6 pay_status = serializers.CharField(read_only=True) 7 order_sn = serializers.CharField(read_only=True) 8 trade_no = serializers.CharField(read_only=True) 9 pay_time = serializers.DateTimeField(read_only=True) 10 add_time = serializers.DateTimeField(read_only=True) 11 alipay_url = serializers.SerializerMethodField(read_only=True) 12 13 def get_alipay_url(self, obj): 14 ''' 15 生成url 16 :return: 17 ''' 18 alipay = AliPay( 19 # 沙箱环境appid 20 appid="2016091800536621", 21 app_notify_url="http://47.106.211.59:8008/alipay/return/", 22 # 私钥路径 23 app_private_key_path=private_key_path, 24 alipay_public_key_path=ali_pub_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 25 debug=True, # 默认False, 26 return_url="http://47.106.211.59:8008/alipay/return/" 27 ) 28 url = alipay.direct_pay( 29 subject=obj.order_sn, 30 out_trade_no=obj.order_sn, 31 total_amount=obj.order_mount, 32 # return_url="http://47.106.211.59:8008/alipay/return/" 33 ) 34 re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) 35 return re_url 36 37 class Meta: 38 model = OrderInfo 39 fields = "__all__" 40 41 def generate_order_sn(self): 42 # 生成订单号(当前时间加用户id加随机数) 43 random_ins = random.Random() 44 order_sn = "{timestr}{userid}{ranstr}".format(timestr=time.strftime("%Y%m%d%H%M%S"), 45 userid=self.context['request'].user.id, 46 ranstr=random_ins.randint(10, 99)) 47 return order_sn 48 49 def validate(self, attrs): 50 attrs['order_sn'] = self.generate_order_sn() 51 return attrs
订单详情序列化:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class OrderDetaiSerializer(serializers.ModelSerializer): goods = OrderGoodsSerializer(many=True) alipay_url = serializers.SerializerMethodField(read_only=True) def get_alipay_url(self, obj): ''' 生成url :return: ''' alipay = AliPay( # 沙箱环境appid appid="2016091800536621", app_notify_url="http://47.106.211.59:8008/alipay/return/", # 私钥路径 app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://47.106.211.59:8008/alipay/return/" ) url = alipay.direct_pay( subject=obj.order_sn, out_trade_no=obj.order_sn, total_amount=obj.order_mount, # return_url="http://47.106.211.59:8008/alipay/return/" ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url class Meta: model = OrderInfo fields = "__all__"
5.注意:一般支付都是图片放在网页中,需要前端多做一个页面,也可以用django做代理页面,使用cnpm run build生成相应文件,放入django项目中,相应配置。
二.微信支付
待写