Django REST framework+Vue 打造生鮮電商專案(筆記九)
(from:http://www.cnblogs.com/derek1184405959/p/8859309.html)
十二、支付寶沙箱環境配置
12.1.建立應用
進入螞蟻金服開放平臺(https://open.alipay.com/platform/home.htm),登入後進入管理中心-->>應用列表
建立應用
建立應用後會有一個appid。還需要提交資訊進行稽核。微信支付和支付寶支付都是要求企業認證才可以完成的,個人開發不可以,所以我們需要用
沙箱環境,它可以讓我們不具備這些應用或者說應用稽核還沒通過的時候先開發除錯
12.2.沙箱環境
沙箱應用地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info
(1)公鑰和私鑰的生成方法
地址:https://docs.open.alipay.com/291/105971/
選winwods,如果linux就選linux
下載好工具照著生成就可以了
(2) 把生成的公鑰和私鑰拷貝到trade/keys下面--->>>重新命名--->>首位各新增下面的內容
-----BEGIN PRIVATE KEY----- -----END PRIVATE KEY-----
(3)把支付寶公鑰也拷貝到這路徑下面,同樣首尾新增
12.3.文件說明
我們主要用到電腦網站支付,文件地址:https://docs.open.alipay.com/270
用到的API介面:統一收單下單並支付頁面介面
裡面有文件說明
幾個比較重要的引數
sign簽名
應該看未使用開放平臺的SDK裡面的說明:https://docs.open.alipay.com/291/106118
請求引數
必填的幾個:
12.4.編寫程式碼
把環境改成本地的
首先的安裝一個模組
pip install pycryptodome
utils中新建alipay.py,程式碼如下:
# apps/utils.py
import json
from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from base64 import b64encode, b64decode from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from urllib.request import urlopen from base64 import decodebytes, encodebytes class AliPay(object): """ 支付寶支付介面 """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url #私鑰 self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) #公鑰 self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.import_key(fp.read()) if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): #請求引數 biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } #允許傳遞更多引數,放到biz_content biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): #build_body主要生產訊息的格式 #公共請求引數 data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): #簽名 data.pop("sign", None) # 排序後的字串 unsigned_items = self.ordered_data(data) #排完序後拼接起來 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) #這裡得到簽名的字串 sign = self.sign(unsigned_string.encode("utf-8")) #對url進行處理 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 獲得最終的訂單資訊字串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string #引數傳進來一定要排序 def ordered_data(self, data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 將字典型別的資料dump出來 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 開始計算簽名 key = self.app_private_key #簽名的物件 signer = PKCS1_v1_5.new(key) #生成簽名 signature = signer.sign(SHA256.new(unsigned_string)) # base64 編碼,轉換為unicode表示並移除回車 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 開始計算簽名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序後的字串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature) if __name__ == "__main__": 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' o = urlparse(return_url) query = parse_qs(o.query) processed_query = {} ali_sign = query.pop("sign")[0] # 測試用例 alipay = AliPay( # 沙箱裡面的appid值 appid="2016091500517456", #notify_url是非同步的url app_notify_url="http://127.0.0.1:8000/", # 我們自己商戶的金鑰 app_private_key_path="../trade/keys/private_2048.txt", # 支付寶的公鑰 alipay_public_key_path="../trade/keys/alipay_key_2048.txt", # 支付寶的公鑰,驗證支付寶回傳訊息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 預設False, return_url="http://127.0.0.1:8000/alipay/return/" ) for key, value in query.items(): processed_query[key] = value[0] # print (alipay.verify(processed_query, ali_sign)) # 直接支付:生成請求的字串。 url = alipay.direct_pay( # 訂單標題 subject="測試訂單derek", # 我們商戶自行生成的訂單號 out_trade_no="20180417derek", # 訂單金額 total_amount=100, #成功付款後跳轉到的頁面,return_url同步的url # return_url="http://127.0.0.1:8000/" ) # 將生成的請求字串拿到我們的url中進行拼接 re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) print(re_url)
執行alipay.py,會生成一個支付連結url,點進去跳到支付頁面
點“登入賬戶付款”,用沙箱賬號付款
輸入賬號密碼以及支付密碼,成功付款
12.5.django整合支付寶notify_url和return_url
(1)配置url
# 配置支付寶支付相關介面的url
path('alipay/return/', AlipayView.as_view())
(2)alipay.py
把return_url和notify_url都改成遠端伺服器的地址
return_url="http://47.93.198.159:8000/alipay/return/"
app_notify_url="http://47.93.198.159:8000/alipay/return/"
alipay.py
(3)settings.py
settings中配置公鑰私鑰路徑
# 支付寶相關的key
private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/private_2048.txt') ali_pub_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/alipay_key_2048.txt')
(4)trade/views.py
from datetime import datetime
from utils.alipay import AliPay from rest_framework.views import APIView from MxShop.settings import ali_pub_key_path, private_key_path from rest_framework.response import Response class AlipayView(APIView): def get(self, request): """ 處理支付寶的return_url返回 """ processed_dict = {} # 1. 獲取GET中引數 for key, value in request.GET.items(): processed_dict[key] = value # 2. 取出sign sign = processed_dict.pop("sign", None) # 3. 生成ALipay物件 alipay = AliPay( appid="2016091500517456", app_notify_url="http://47.93.198.159:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳訊息使用,不是你自己的公鑰, debug=True, # 預設False, return_url="http://47.93.198.159:8000/alipay/return/" ) verify_re = alipay.verify(processed_dict, sign) # 這裡可以不做操作。因為不管發不發return url。notify url都會修改訂單狀態。 if verify_re is True: order_sn = processed_dict.get('out_trade_no', None) trade_no = processed_dict.get('trade_no', None) trade_status = processed_dict.get('trade_status', None) existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: existed_order.pay_status = trade_status existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save() def post(self, request): """ 處理支付寶的notify_url """ #存放post裡面所有的資料 processed_dict = {} #取出post裡面的資料 for key, value in request.POST.items(): processed_dict[key] = value #把signpop掉,文件有說明 sign = processed_dict.pop("sign", None) #生成一個Alipay物件 alipay = AliPay( appid="2016091500517456", app_notify_url="http://47.93.198.159:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳訊息使用,不是你自己的公鑰, debug=True, # 預設False, return_url="http://47.93.198.159:8000/alipay/return/" ) #進行驗證 verify_re = alipay.verify(processed_dict, sign) # 如果驗籤成功 if verify_re is True: #商戶網站唯一訂單號 order_sn = processed_dict.get('out_trade_no', None) #支付寶系統交易流水號 trade_no = processed_dict.get('trade_no', None) #交易狀態 trade_status = processed_dict.get('trade_status', None) # 查詢資料庫中訂單記錄 existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: # 訂單商品項 order_goods = existed_order.goods.all() # 商品銷量增加訂單中數值 for order_good in order_goods: goods = order_good.goods goods.sold_num += order_good.goods_num goods.save() # 更新訂單狀態 existed_order.pay_status = trade_status existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save() #需要返回一個'success'給支付寶,如果不返回,支付寶會一直髮送訂單支付成功的訊息 return Response("success")
(5)trade/serializers.py
建立訂單的時候生成一個支付的url,這個邏輯OderSerializer和OrderDetailSerializer中都新增
#支付訂單的url
alipay_url = serializers.SerializerMethodField(read_only=True)
def get_alipay_url(self, obj):
alipay = AliPay( appid="2016091500517456", app_notify_url="http://47.93.198.159:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳訊息使用,不是你自己的公鑰, debug=True, # 預設False, return_url="http://47.93.198.159:8000/alipay/return/" ) url = alipay.direct_pay( subject=obj.order_sn, out_trade_no=obj.order_sn, total_amount=obj.order_mount, ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url
trade/serializer.py
(6)測試程式碼
把本地修改的地方一定要上傳到伺服器,因為我們需要在伺服器上除錯程式碼
vue專案中api.js裡面local_host改為伺服器ip
let local_host = 'http://47.93.198.159:8000';
在phcharm中開始執行專案
瀏覽器訪問地址:http://47.93.198.159:8000/orders/
建立一個訂單
生成訂單的詳情
12.6.vue靜態檔案放到django中
vue有兩種開發模式
- build 用來生成靜態檔案
- dev
(1)執行
cnpm run build
生成的靜態檔案在dist目錄下面
(2)把index.html拷貝到templates目錄下
(3)django中建立static目錄
- 把index.entry.js考到django的static目錄下面
- 把dist/static下的兩個資料夾拷貝到django static目錄下
(4)settings設定靜態檔案路徑
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"), )
(5)修改index.html中靜態檔案路徑
<script type="text/javascript" src="/static/index.entry.js"></script></body>
(6)配置index的url
from django.views.generic import TemplateView
urlpatterns = [
# 首頁 path('index/', TemplateView.as_view(template_name='index.html'),name='index') ]
(7)配置支付成功return的地址
trade/views.py
response = redirect("/index/#/app/home/member/order")
return response else: response = redirect("index") return response
# trade/views.py
現在可以通過index直接訪問了:http://47.93.198.159:8000/index/#/app/home/index
新增商品到購物車-->>去結算-->>生成訂單-->>跳到支付頁面了