(專案)生鮮超市(十)
十一、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_qs9 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,然後登陸新增商品到購物車進行結算,跳轉到支付寶支付頁面,支付成功跳轉到首頁。