1. 程式人生 > >(專案)生鮮超市(十)

(專案)生鮮超市(十)

十一、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&timestamp=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,然後登陸新增商品到購物車進行結算,跳轉到支付寶支付頁面,支付成功跳轉到首頁。