(项目)生鲜超市(十)
十一、pycharm远程代码调试
第三方登录和支付,都需要有线上服务器才行(需要回调url),我们可以用pycharm去远程调试服务器代码。
首先需要一台云服务器,我用的是腾讯云的服务器,pycharm远程连接服务器及解释器的方法这里不细讲,如果有不懂的童靴可以私聊我,我会发视频给你。
十二、支付宝沙箱环境配置
订单结算是通过支付宝进行支付的,这里测试使用蚂蚁金服支付宝的沙箱环境测试支付流程,沙箱环境的配置也不细讲,如有需要请联系我发视频。
将解释器的环境暂时改成本地进行调试,然后在utils下新建alipay.py文件,编写生成支付宝支付接口的url脚本,在编写之前需要pip install pycryptodome,这个库主要用来对密钥进行签名,编写代码:
1 import json 2 from datetime import datetime 3 from Crypto.PublicKey import RSA 4 from Crypto.Signature import PKCS1_v1_5 5 from Crypto.Hash import SHA256 6 from base64 import b64encode, b64decode 7 from urllib.parse import quote_plus 8 from urllib.parse import urlparse, parse_qs 9 from urllib.request import urlopen 10 from base64 import decodebytes, encodebytes 11 12 13 class AliPay(object): 14 """ 15 支付宝支付接口 16 """ 17 18 def __init__(self, appid, app_notify_url, app_private_key_path, 19 alipay_public_key_path, return_url, debug=False): 20 self.appid = appid 21 self.app_notify_url = app_notify_url 22 # 私钥 23 self.app_private_key_path = app_private_key_path 24 self.app_private_key = None 25 self.return_url = return_url 26 with open(self.app_private_key_path) as fp: 27 self.app_private_key = RSA.importKey(fp.read()) 28 # 公钥 29 self.alipay_public_key_path = alipay_public_key_path 30 with open(self.alipay_public_key_path) as fp: 31 self.alipay_public_key = RSA.import_key(fp.read()) 32 33 if debug is True: 34 self.__gateway = "https://openapi.alipaydev.com/gateway.do" 35 else: 36 self.__gateway = "https://openapi.alipay.com/gateway.do" 37 38 def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): 39 # 请求参数 40 biz_content = { 41 "subject": subject, 42 "out_trade_no": out_trade_no, 43 "total_amount": total_amount, 44 "product_code": "FAST_INSTANT_TRADE_PAY", 45 # "qr_pay_mode":4 46 } 47 # 允许传递更多参数,放到biz_content 48 biz_content.update(kwargs) 49 data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) 50 return self.sign_data(data) 51 52 def build_body(self, method, biz_content, return_url=None): 53 # build_body主要生产消息的格式 54 # 公共请求参数 55 data = { 56 "app_id": self.appid, 57 "method": method, 58 "charset": "utf-8", 59 "sign_type": "RSA2", 60 "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 61 "version": "1.0", 62 "biz_content": biz_content 63 } 64 65 if return_url is not None: 66 data["notify_url"] = self.app_notify_url 67 data["return_url"] = self.return_url 68 69 return data 70 71 def sign_data(self, data): 72 # 签名 73 data.pop("sign", None) 74 # 排序后的字符串 75 unsigned_items = self.ordered_data(data) 76 # 排完序后拼接起来 77 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) 78 # 这里得到签名的字符串 79 sign = self.sign(unsigned_string.encode("utf-8")) 80 # 对url进行处理 81 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) 82 83 # 获得最终的订单信息字符串 84 signed_string = quoted_string + "&sign=" + quote_plus(sign) 85 return signed_string 86 87 # 参数传进来一定要排序 88 def ordered_data(self, data): 89 complex_keys = [] 90 for key, value in data.items(): 91 if isinstance(value, dict): 92 complex_keys.append(key) 93 94 # 将字典类型的数据dump出来 95 for key in complex_keys: 96 data[key] = json.dumps(data[key], separators=(',', ':')) 97 98 return sorted([(k, v) for k, v in data.items()]) 99 100 def sign(self, unsigned_string): 101 # 开始计算签名 102 key = self.app_private_key 103 # 签名的对象 104 signer = PKCS1_v1_5.new(key) 105 # 生成签名 106 signature = signer.sign(SHA256.new(unsigned_string)) 107 # base64 编码,转换为unicode表示并移除回车 108 sign = encodebytes(signature).decode("utf8").replace("\n", "") 109 return sign 110 111 def _verify(self, raw_content, signature): 112 # 开始计算签名 113 key = self.alipay_public_key 114 signer = PKCS1_v1_5.new(key) 115 digest = SHA256.new() 116 digest.update(raw_content.encode("utf8")) 117 if signer.verify(digest, decodebytes(signature.encode("utf8"))): 118 return True 119 return False 120 121 def verify(self, data, signature): 122 if "sign_type" in data: 123 sign_type = data.pop("sign_type") 124 # 排序后的字符串 125 unsigned_items = self.ordered_data(data) 126 message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) 127 return self._verify(message, signature) 128 129 130 if __name__ == "__main__": 131 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' 132 o = urlparse(return_url) 133 query = parse_qs(o.query) 134 processed_query = {} 135 ali_sign = query.pop("sign")[0] 136 137 # 测试用例 138 alipay = AliPay( 139 # 沙箱里面的appid值 140 appid="2016092000557473", 141 # notify_url是异步的url 142 app_notify_url="http://127.0.0.1:8000/", 143 # 我们自己商户的密钥 144 app_private_key_path="../trade/keys/private_2048.txt", 145 # 支付宝的公钥 146 alipay_public_key_path="../trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 147 # debug为true时使用沙箱的url。如果不是用正式环境的url 148 debug=True, # 默认False, 149 return_url="http://127.0.0.1:8000/alipay/return/" 150 ) 151 152 for key, value in query.items(): 153 processed_query[key] = value[0] 154 # print (alipay.verify(processed_query, ali_sign)) 155 156 # 直接支付:生成请求的字符串。 157 url = alipay.direct_pay( 158 # 订单标题 159 subject="测试订单wj", 160 # 我们商户自行生成的订单号 161 out_trade_no="2018041721312", 162 # 订单金额 163 total_amount=100, 164 # 成功付款后跳转到的页面,return_url同步的url 165 # return_url="http://127.0.0.1:8000/" 166 ) 167 # 将生成的请求字符串拿到我们的url中进行拼接 168 re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) 169 170 print(re_url)
直接运行该文件,会生成一段支付宝支付接口的url,点击这个url,会跳转到支付宝支付接口,将支付宝提供给你的沙箱环境中的买家账户进行付款测试:
现在由django去集成支付宝的notify_url和return_url,首先配置支付宝接口的url:
1 path('alipay/return/', AlipayView.as_view()), # 支付宝接口
在alipay.py中,将return_url和notify_url都改成远程服务器的地址:
1 app_notify_url="http://148.70.2.75:8000/alipay/return/" 2 return_url="http://148.70.2.75:8000/alipay/return/"
在settings中配置公钥私钥路径:
1 # 支付宝相关的key 2 private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/private_2048.txt') 3 ali_pub_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/alipay_key_2048.txt')
在trade/views.py中编写支付宝的接口:
1 class AlipayView(APIView): 2 """支付宝接口""" 3 4 # 处理支付宝的return_url返回 5 def get(self, request): 6 processed_dict = {} 7 8 # 获取GET中的参数 9 for key, value in request.GET.items(): 10 processed_dict[key] = value 11 12 # 从processed_dict中取出sign 13 sign = processed_dict.pop("sign", None) 14 15 # 生成AliPay对象 16 alipay = AliPay( 17 appid="2016092000557473", 18 app_notify_url="http://148.70.2.75:8000/alipay/return/", 19 app_private_key_path=private_key_path, 20 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 21 debug=True, # 默认False, 22 return_url="http://148.70.2.75:8000/alipay/return/" 23 ) 24 25 # 验证签名 26 verify_re = alipay.verify(processed_dict, sign) 27 28 # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。 29 if verify_re is True: 30 order_sn = processed_dict.get('out_trade_no', None) 31 trade_no = processed_dict.get('trade_no', None) 32 trade_status = processed_dict.get('trade_status', None) 33 34 existed_orders = OrderInfo.objects.filter(order_sn=order_sn) 35 for existed_order in existed_orders: 36 existed_order.pay_status = trade_status 37 existed_order.trade_no = trade_no 38 existed_order.pay_time = datetime.now() 39 existed_order.save() 40 41 # 处理支付宝的notify_url 42 def post(self, request): 43 processed_dict = {} 44 45 # 取出post里面的数据 46 for key, value in request.POST.items(): 47 processed_dict[key] = value 48 49 # 去掉sign 50 sign = processed_dict.pop("sign", None) 51 52 # 生成一个Alipay对象 53 alipay = AliPay( 54 appid="2016092000557473", 55 app_notify_url="http://148.70.2.75:8000/alipay/return/", 56 app_private_key_path=private_key_path, 57 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 58 debug=True, # 默认False, 59 return_url="http://148.70.2.75:8000/alipay/return/" 60 ) 61 62 # 进行验证 63 verify_re = alipay.verify(processed_dict, sign) 64 65 if verify_re is True: 66 # 商户网站唯一订单号 67 order_sn = processed_dict.get('out_trade_no', None) 68 # 支付宝系统交易流水号 69 trade_no = processed_dict.get('trade_no', None) 70 # 交易状态 71 trade_status = processed_dict.get('trade_status', None) 72 73 # 查询数据库中订单记录 74 existed_orders = OrderInfo.objects.filter(order_sn=order_sn) 75 for existed_order in existed_orders: 76 # 订单商品项 77 order_goods = existed_order.goods.all() 78 # 商品销量增加订单中数值 79 for order_good in order_goods: 80 goods = order_good.goods 81 goods.sold_num += order_good.goods_num 82 goods.save() 83 84 # 更新订单状态 85 existed_order.pay_status = trade_status 86 existed_order.trade_no = trade_no 87 existed_order.pay_time = datetime.now() 88 existed_order.save() 89 # 需要返回一个'success'给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息 90 return Response("success")
创建订单的时候生成一个支付的url,这个逻辑OderSerializer和OrderDetailSerializer中都添加:
1 class OrderDetailSerializer(serializers.ModelSerializer): 2 # goods字段需要嵌套一个OrderGoodsSerializer 3 goods = OrderGoodsSerializer(many=True) 4 # 支付订单的url 5 alipay_url = serializers.SerializerMethodField(read_only=True) 6 7 def get_alipay_url(self, obj): 8 alipay = AliPay( 9 appid="2016092000557473", 10 app_notify_url="http://148.70.2.75:8000/alipay/return/", 11 app_private_key_path=private_key_path, 12 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 13 debug=True, # 默认False, 14 return_url="http://148.70.2.75:8000/alipay/return/" 15 ) 16 17 url = alipay.direct_pay( 18 subject=obj.order_sn, 19 out_trade_no=obj.order_sn, 20 total_amount=obj.order_mount, 21 ) 22 re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) 23 24 return re_url 25 26 class Meta: 27 model = OrderInfo 28 fields = "__all__" 29 30 31 class OrderSerializer(serializers.ModelSerializer): 32 user = serializers.HiddenField( 33 default=serializers.CurrentUserDefault() 34 ) 35 # 生成订单的时候这些不用post 36 pay_status = serializers.CharField(read_only=True) 37 trade_no = serializers.CharField(read_only=True) 38 order_sn = serializers.CharField(read_only=True) 39 pay_time = serializers.DateTimeField(read_only=True) 40 nonce_str = serializers.CharField(read_only=True) 41 pay_type = serializers.CharField(read_only=True) 42 # 支付订单的url 43 alipay_url = serializers.SerializerMethodField(read_only=True) 44 45 def get_alipay_url(self, obj): 46 alipay = AliPay( 47 appid="2016092000557473", 48 app_notify_url="http://148.70.2.75:8000/alipay/return/", 49 app_private_key_path=private_key_path, 50 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 51 debug=True, # 默认False, 52 return_url="http://148.70.2.75:8000/alipay/return/" 53 ) 54 55 url = alipay.direct_pay( 56 subject=obj.order_sn, 57 out_trade_no=obj.order_sn, 58 total_amount=obj.order_mount, 59 ) 60 re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) 61 62 return re_url 63 64 # 生成订单号 65 def generate_order_sn(self): 66 # 格式:当前时间+userid+随机数 67 random_ins = Random() 68 order_sn = '{time_str}{userid}{ranstr}'.format(time_str=time.strftime("%Y%m%d%H%M%S"), 69 userid=self.context["request"].user.id, 70 ranstr=random_ins.randint(10, 99)) 71 return order_sn 72 73 def validate(self, attrs): 74 # validate中添加order_sn,然后在view中就可以save 75 attrs['order_sn'] = self.generate_order_sn() 76 return attrs 77 78 class Meta: 79 model = OrderInfo 80 fields = "__all__"
然后将本地修改的地方一定要上传到服务器上,在服务器上调试代码,将Vue中de的api.js中的host改成线上的地址:
1 let host = 'http://148.70.2.75:8000';
然后在pycharm中运行项目,点击订单的接口创建一个订单:
十三、将Vue的静态文件放到django中
vue有两种开发模式:
- build(用来生成静态文件)
- dev(开发模式)
在前端Vue项目目录下,运行:cnpm run build
运行之后,会生成项目的静态文件:
把index.html文件拷贝到项目templates目录下,在项目根目录下新建static文件,将下面的文件拷贝过来:
在settings中配置静态文件的路径:
1 STATIC_URL = '/static/' 2 STATICFILES_DIRS = ( 3 os.path.join(BASE_DIR, "static"), 4 )
修改index.html中静态文件路径:
配置index的url:
1 path('index/', TemplateView.as_view(template_name='index.html'), name='index') # 首页
在trade/views.py中配置支付成功return的地址:
1 class AlipayView(APIView): 2 """支付宝接口""" 3 4 # 处理支付宝的return_url返回 5 def get(self, request): 6 processed_dict = {} 7 8 # 获取GET中的参数 9 for key, value in request.GET.items(): 10 processed_dict[key] = value 11 12 # 从processed_dict中取出sign 13 sign = processed_dict.pop("sign", None) 14 15 # 生成AliPay对象 16 alipay = AliPay( 17 appid="2016092000557473", 18 app_notify_url="http://148.70.2.75:8000/alipay/return/", 19 app_private_key_path=private_key_path, 20 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 21 debug=True, # 默认False, 22 return_url="http://148.70.2.75:8000/alipay/return/" 23 ) 24 25 # 验证签名 26 verify_re = alipay.verify(processed_dict, sign) 27 28 # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。 29 if verify_re is True: 30 order_sn = processed_dict.get('out_trade_no', None) 31 trade_no = processed_dict.get('trade_no', None) 32 trade_status = processed_dict.get('trade_status', None) 33 34 existed_orders = OrderInfo.objects.filter(order_sn=order_sn) 35 for existed_order in existed_orders: 36 existed_order.pay_status = trade_status 37 existed_order.trade_no = trade_no 38 existed_order.pay_time = datetime.now() 39 existed_order.save() 40 41 # 支付完成跳转到首页 42 response = redirect("index") 43 response.set_cookie("nextPath", "pay", max_age=2) 44 return response 45 else: 46 response = redirect("index") 47 return response 48 49 # 处理支付宝的notify_url 50 def post(self, request): 51 processed_dict = {} 52 53 # 取出post里面的数据 54 for key, value in request.POST.items(): 55 processed_dict[key] = value 56 57 # 去掉sign 58 sign = processed_dict.pop("sign", None) 59 60 # 生成一个Alipay对象 61 alipay = AliPay( 62 appid="2016092000557473", 63 app_notify_url="http://148.70.2.75:8000/alipay/return/", 64 app_private_key_path=private_key_path, 65 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 66 debug=True, # 默认False, 67 return_url="http://148.70.2.75:8000/alipay/return/" 68 ) 69 70 # 进行验证 71 verify_re = alipay.verify(processed_dict, sign) 72 73 if verify_re is True: 74 # 商户网站唯一订单号 75 order_sn = processed_dict.get('out_trade_no', None) 76 # 支付宝系统交易流水号 77 trade_no = processed_dict.get('trade_no', None) 78 # 交易状态 79 trade_status = processed_dict.get('trade_status', None) 80 81 # 查询数据库中订单记录 82 existed_orders = OrderInfo.objects.filter(order_sn=order_sn) 83 for existed_order in existed_orders: 84 # 订单商品项 85 order_goods = existed_order.goods.all() 86 # 商品销量增加订单中数值 87 for order_good in order_goods: 88 goods = order_good.goods 89 goods.sold_num += order_good.goods_num 90 goods.save() 91 92 # 更新订单状态 93 existed_order.pay_status = trade_status 94 existed_order.trade_no = trade_no 95 existed_order.pay_time = datetime.now() 96 existed_order.save() 97 # 需要返回一个'success'给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息 98 return Response("success")
现在可以通过index直接访问了:http://148.70.2.75:8000/index,然后登陆添加商品到购物车进行结算,跳转到支付宝支付页面,支付成功跳转到首页。