支付的大體流程以下圖:前端
部分節點詳解:vue
python
沙箱環境跟真實環境是分開的,項目上線時必須切換對應的配置服務器地址和開發者ID和密鑰。ios
沙箱應用:https://docs.open.alipay.comgit
沙箱帳號:https://openhome.alipay.com/platform/appDaily.htm?tab=accountgithub
真實的支付寶網關: https://openapi.alipay.com/gateway.do 沙箱的支付寶網關: https://openapi.alipaydev.com/gateway.do
產品介紹:https://docs.open.alipay.com/270django
cd luffy/apps
python ../../manage.py startapp payments
json
axios
在payments應用中新建keys目錄,用來保存祕鑰文件。
將應用私鑰文件app_private_key.pem複製到payment/keys目錄下。
-----BEGIN RSA PRIVATE KEY-----
私鑰
-----END RSA PRIVATE KEY-----
在payment/keys目錄下新建alipay_public_key.pem文件,用於保存支付寶的公鑰文件。
將支付寶的公鑰內容複製到alipay_public_key.pem文件中
-----BEGIN PUBLIC KEY-----
公鑰
-----END PUBLIC KEY-----
SDK:https://docs.open.alipay.com/270/106291/
python版本的支付寶SDK文檔:https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
安裝命令:
pip install python-alipay-sdk --upgrade
<template> ..... <el-col :span="4" class="cart-pay" ><span @click="payhandler" >支付寶支付</span></el-col> .... </template> <script> export default { name:"Order", methods:{ payhandler(){ // 判斷用戶是否已經登錄了。 if( !this.token){ this.$router.push("/login"); } let _this = this; // 發起請求獲取購物車中的商品信息 _this.$axios.get("http://127.0.0.1:8000/pay/"+_this.order_id,{ headers: { 'Authorization': 'JWT ' + _this.token }, responseType: 'json', }).then(response=>{ console.log("response.data",response.data.url) //跳轉頁面到支付界面 window.location.href=response.data.url; }) } }, } </script>
class PaymentAPIView(APIView): """支付寶""" permission_classes = (IsAuthenticated,) def get(self, request, order_id): """獲取支付連接""" # 判斷訂單信息是否正確 try: order = Order.objects.get(order_id=order_id, user=request.user, order_status=0,) except Order.DoesNotExist: return Response({'message': '訂單信息有誤'}, status=status.HTTP_400_BAD_REQUEST) # 構造支付寶支付連接地址 alipay = AliPay( appid=settings.ALIPAY_APPID, app_notify_url=None, # 默認回調url app_private_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem"), alipay_public_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/alipay_public_key.pem"), # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你本身的公鑰, sign_type="RSA2", # RSA 或者 RSA2 debug=settings.ALIPAY_DEBUG ) order_string = alipay.api_alipay_trade_page_pay( out_trade_no=order.id, total_amount=str(order.total_price), subject=order.order_desc, return_url="http://127.0.0.1:8080/pay_success", ) alipay_url = settings.ALIPAY_URL + "?" + order_string return Response({'alipay_url': alipay_url}, status=status.HTTP_201_CREATED)
# 支付寶 ALIPAY_APP_ID="2016091600523592" # 應用ID APLIPAY_APP_NOTIFY_URL = None # 應用回調地址[支付成功之後,支付寶返回結果到哪個地址下面] APP_PRIVATE_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/app_private_key.pem") ALIPAY_PUBLIC_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/alipay_public_key.pem") ALIPAY_DEBUG = True # APIPAY_GATEWAY="https://openapi.alipay.com/gateway.do" #上線使用 APIPAY_GATEWAY="https://openapi.alipaydev.com/gateway.do" #開發環境使用 ALIPAY_RETURN_URL = "http://127.0.0.1:8080/success" ALIPAY_NOTIFY_URL = "http://127.0.0.1:8080/success"
success.vue 前端發起請求頁面:..
<template> ..... </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default{ created(){ // 頁面刷新時,最開始時候要把支付寶服務器的返回get參數結果提交給後端 // 後端須要根據這結果修改訂單的狀態 this.$axios.get("http://127.0.0.1:8000/pay/result"+location.search,{ headers: { 'Authorization': 'JWT ' + this.token }, responseType: 'json', }).then(response=>{ this.result = response.data }).catch(error=>{ console.log(error.response); }) </script>
注意,此時後端發送的數據,必需要攜帶支付寶在表頭返回的數據,以用於後端驗證
支付寶會返回的參數以下列表:(前面是域名,後面纔是參數)
http://127.0.0.1:8080/success?charset=utf-8& out_trade_no=2019040217080000000010976& method=alipay.trade.page.pay.return& total_amount=1206.44& sign=XKJG5826fH%2F9%2B3jCWw2ODjlc%2FuGLfqmr5RnimSAqrh%2B5bFkWcbLDh5V6VYtMqCpwnYp3FuGPqEeUeRO6WK62Qz0Q5nQGOA394IdxPfTOzry7PXuwYf41PCbDq53yg7vCYrobz4Tt8uajeADJLJwIsL%2F%2B88vbDEISUDUujL4442kl3oLh3EDD8DxZc2LLsv1Z%2FEFGJMfcTA47A4T7qmjB%2BbLKJetZZBISdt9RDL0q8A%2BAfb8B3Ux1nq%2F0EiNGiwIlWC1pvUCHK2UXMJW3kmgU9P9Zoujrj4ER28oieQt6Rt4gQXeah5uYtAMkftWfZpiyu%2FjUkr6iRx%2B4mP5IFz4Uew%3D%3D& trade_no=2019040222001439881000005802& auth_app_id=2016091600523592& version=1.0& app_id=2016091600523592& sign_type=RSA2& seller_id=2088102175868026& timestamp=2019-04-02%2017%3A13%3A15
後端代碼以下:
1 class PaymentAPIView(APIView): 2 ....... 3 4 5 class PayResultAPIView(APIView): 6 def get(self,request): 7 """處理get返回結果的數據""" 8 # 接受數據 9 data = request.query_params.dict() 10 print(data) 11 # sign 不能參與簽名驗證 12 signature = data.pop("sign") 13 # print(json.dumps(data)) 14 # print(signature) 15 alipay = AliPay( 16 appid=settings.ALIPAY_APP_ID, 17 app_notify_url=None, # 默認回調url 18 # 應用私鑰 19 app_private_key_path=settings.APP_PRIVATE_KEY_PATH, 20 # 支付寶的公鑰, 21 alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH, 22 sign_type="RSA2", # 密碼加密的算法 23 # 開發時屬於調試模式 24 debug = settings.ALIPAY_DEBUG # 默認False 25 ) 26 27 # verify驗證支付結果,布爾值 28 success = alipay.verify(data, signature) 29 30 if success: 31 # 支付成功 32 order = Order.objects.get(order_number=data.get("out_trade_no")) 33 order.order_status = 1 # 修改訂單狀態 34 order.pay_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 35 order.save() 36 37 38 return Response({ 39 "length":order.order_course.count(), 40 "paytime": order.pay_time, 41 "price":order.total_price, 42 "info":order.order_desc, 43 },status=status.HTTP_200_OK) 44 45 return Response({"message":"訂單沒有變化"}) 46 def post(self,request): 47 """提供給支付寶發送post數據""" 48 # 參考上面的代碼實現 49 # 接受數據 50 data = request.data.dict() 51 52 # sign 不能參與簽名驗證 53 signature = data.pop("sign") 54 # print(json.dumps(data)) 55 # print(signature) 56 alipay = AliPay( 57 appid=settings.ALIPAY_APP_ID, 58 app_notify_url=None, # 默認回調url 59 # 應用私鑰 60 app_private_key_path=settings.APP_PRIVATE_KEY_PATH, 61 # 支付寶的公鑰, 62 alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH, 63 sign_type="RSA2", # 密碼加密的算法 64 # 開發時屬於調試模式 65 debug=settings.ALIPAY_DEBUG # 默認False 66 ) 67 68 # verify驗證支付結果,布爾值 69 success = alipay.verify(data, signature) 70 71 if success: 72 # 支付成功 73 order = Order.objects.get(order_number=data.get("out_trade_no")) 74 order.order_status = 1 # 修改訂單狀態 75 order.pay_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 76 order.save() 77 78 return Response({ 79 "length": order.order_course.count(), 80 "paytime": order.pay_time, 81 "price": order.total_price, 82 "info": order.order_desc, 83 }, status=status.HTTP_200_OK) 84 85 return Response({"message": "訂單沒有變化"})
完整代碼,可詳細看:
前端代碼:
success.vue
<template> <div class="success"> <Header :current_page="current_page"/> <div class="main"> <div class="title"> <img src="../../static/images/right.svg" alt=""> <div class="success-tips"> <p class="tips1">您已成功購買 {{result.length}} 門課程!</p> <p class="tips2">你還能夠加入QQ羣 <span>747556033</span> 學習交流</p> </div> </div> <div class="order-info"> <p class="info1"><b>付款時間:</b><span>{{result.paytime}}</span></p> <p class="info2"><b>付款金額:</b><span >{{result.price}}</span></p> <p class="info3"><b>課程信息:</b><span><span>《{{result.info}}》</span></span></p> </div> <div class="wechat-code"> <img src="../../static/images/server.cf99f78.png" alt="" class="er"> <p><img src="../../static/images/tan.svg" alt="">重要!微信掃碼關注得到學習通知&課程更新提醒!不然將嚴重影響學習進度和課程體驗!</p> </div> <div class="study"> <span>當即學習</span> </div> </div> <Footer/> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default{ name:"Success", data(){ return { token: sessionStorage.token || localStorage.token, current_page:0, result:{}, }; }, components:{ Header, Footer, }, created(){ // 頁面刷新時,最開始時候要把支付寶服務器的返回get參數結果提交給後端 // 後端須要根據這結果修改訂單的狀態 this.$axios.get("http://127.0.0.1:8000/pay/result"+location.search,{ headers: { 'Authorization': 'JWT ' + this.token }, responseType: 'json', }).then(response=>{ this.result = response.data }).catch(error=>{ console.log(error.response); }) } } </script> <style scoped> .success{ padding-top: 80px; } .main{ height: 100%; padding-top: 25px; padding-bottom: 25px; margin: 0 auto; width: 1200px; background: #fff; } .main .title{ display: flex; -ms-flex-align: center; align-items: center; padding: 25px 40px; border-bottom: 1px solid #f2f2f2; } .main .title .success-tips{ box-sizing: border-box; } .title img{ vertical-align: middle; width: 60px; height: 60px; margin-right: 40px; } .title .success-tips{ box-sizing: border-box; } .title .tips1{ font-size: 22px; color: #000; } .title .tips2{ font-size: 16px; color: #4a4a4a; letter-spacing: 0; text-align: center; margin-top: 10px; } .title .tips2 span{ color: #ec6730; } .order-info{ padding: 25px 48px; padding-bottom: 15px; border-bottom: 1px solid #f2f2f2; } .order-info p{ font-family: PingFangSC-Regular; display: -ms-flexbox; display: flex; margin-bottom: 10px; font-size: 16px; } .order-info p b{ font-weight: 400; color: #9d9d9d; white-space: nowrap; } .wechat-code{ display: flex; -ms-flex-align: center; align-items: center; padding: 25px 40px; border-bottom: 1px solid #f2f2f2; } .wechat-code>img{ width: 100px; height: 100px; margin-right: 15px; } .wechat-code p{ font-family: PingFangSC-Regular; font-size: 14px; color: #d0021b; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; } .wechat-code p>img{ width: 16px; height: 16px; margin-right: 10px; } .study{ padding: 25px 40px; } .study span{ display: block; width: 140px; height: 42px; text-align: center; line-height: 42px; cursor: pointer; background: #ffc210; border-radius: 6px; font-family: PingFangSC-Regular; font-size: 16px; color: #fff; } </style>
order.vue:
<template> <div class="cart"> <Header/> <div class="cart-info"> <h3 class="cart-top">購物車結算 <span>共1門課程</span></h3> <div class="cart-title"> <el-row> <el-col :span="2"> </el-col> <el-col :span="10">課程</el-col> <el-col :span="8">有效期</el-col> <el-col :span="4">價格</el-col> </el-row> </div> <div class="cart-item" v-for="item in course_list" > <el-row> <el-col :span="2" class="checkbox"> </el-col> <el-col :span="10" class="course-info"> <img :src="item.course.course_http_img" alt=""> <span>{{item.course.name}}</span> </el-col> <el-col :span="8"><span>永久有效</span></el-col> <el-col :span="4" class="course-price">¥{{item.unit_price}}</el-col> </el-row> </div> <div class="calc"> <el-row class="pay-row"> <el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col> <el-col :span="4"><span class="alipay"><img src="../../static/images/1554167287107.png" alt=""></span></el-col> <el-col :span="12" class="count">實付款: <span>¥{{total}}</span></el-col> <el-col :span="4" class="cart-pay" ><span @click="payhandler" >支付寶支付</span></el-col> </el-row> </div> </div> <Footer/> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name:"Order", data(){ return { total:0, course_list:[], token: localStorage.token || sessionStorage.token, id : localStorage.id || sessionStorage.id, order_id:sessionStorage.order_id || null, } }, components:{ Header, Footer, }, methods:{ payhandler(){ // 判斷用戶是否已經登錄了。 if( !this.token){ this.$router.push("/login"); } let _this = this; // 發起請求獲取購物車中的商品信息 _this.$axios.get("http://127.0.0.1:8000/pay/"+_this.order_id,{ headers: { 'Authorization': 'JWT ' + _this.token }, responseType: 'json', }).then(response=>{ console.log("response.data",response.data.url) //跳轉頁面到支付界面 window.location.href=response.data.url; }) } }, created() { // 判斷用戶是否已經登錄了。 if( !this.token){ this.$router.push("/login"); } let _this = this; // 發起請求獲取購物車中的商品信息 _this.$axios.get("http://127.0.0.1:8000/orders/detail/"+_this.order_id,{ headers: { 'Authorization': 'JWT ' + _this.token }, responseType: 'json', }).then(response=>{ console.log("response.data",response.data) _this.course_list = response.data.order_course; _this.total = response.data.total_price }) }, } </script> <style scoped> .cart{ margin-top: 80px; } .cart-info{ overflow: hidden; width: 1200px; margin: auto; } .cart-top{ font-size: 18px; color: #666; margin: 25px 0; font-weight: normal; } .cart-top span{ font-size: 12px; color: #d0d0d0; display: inline-block; } .cart-title{ background: #F7F7F7; height: 70px; } .calc{ margin-top: 25px; margin-bottom: 40px; } .calc .count{ text-align: right; margin-right: 10px; vertical-align: middle; } .calc .count span{ font-size: 36px; color: #333; } .calc .cart-pay{ margin-top: 5px; width: 110px; height: 38px; outline: none; border: none; color: #fff; line-height: 38px; background: #ffc210; border-radius: 4px; font-size: 16px; text-align: center; cursor: pointer; } .cart-item{ height: 120px; line-height: 120px; } .course-info img{ width: 175px; height: 115px; margin-right: 35px; vertical-align: middle; } .alipay{ display: block; height: 48px; } .alipay img{ height: 100%; width:auto; } .pay-text{ display: block; text-align: right; height: 100%; line-height: 100%; vertical-align: middle; margin-top: 20px; } </style>
1 from datetime import datetime 2 3 from alipay import AliPay 4 from django.conf import settings 5 from django.shortcuts import render 6 7 # Create your views here. 8 from rest_framework import status 9 from rest_framework.permissions import IsAuthenticated 10 from rest_framework.response import Response 11 from rest_framework.views import APIView 12 13 from luffy.apps.orders.models import Order 14 15 16 class PaymentAPIView(APIView): 17 permission_classes = [IsAuthenticated] 18 19 def get(self, request, pk): 20 """生成支付連接的地址""" 21 try: 22 order = Order.objects.get(pk=pk) 23 except Order.DoesNotExist(): 24 return Response({"message": "當前訂單不存在!"}, status=status.HTTP_400_BAD_REQUEST) 25 26 alipay = AliPay( 27 appid=settings.ALIPAY_APP_ID, 28 app_notify_url=None, # 默認回調url 29 # 應用私鑰 30 app_private_key_path=settings.APP_PRIVATE_KEY_PATH, 31 # 支付寶的公鑰, 32 alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH, 33 sign_type="RSA2", # 密碼加密的算法 34 # 開發時屬於調試模式 35 debug=settings.ALIPAY_DEBUG # 默認False 36 ) 37 38 # 生成參數 39 order_string = alipay.api_alipay_trade_page_pay( 40 out_trade_no = order.order_number, 41 total_amount = float(order.total_price), # 訂單價格,單位:元 / RMB 42 subject = order.order_desc, # 訂單標題 43 return_url = settings.ALIPAY_RETURN_URL, 44 notify_url=settings.ALIPAY_NOTIFY_URL # 可選, 不填則使用默認notify url 45 ) 46 # 生成新地址 47 url = settings.APIPAY_GATEWAY + "?" + order_string 48 49 return Response({"url": url}, status=status.HTTP_200_OK) 50 51 52 53 class PayResultAPIView(APIView): 54 def get(self,request): 55 """處理get返回結果的數據""" 56 # 接受數據 57 data = request.query_params.dict() 58 # sign 不能參與簽名驗證 59 signature = data.pop("sign") 60 61 alipay = AliPay( 62 appid=settings.ALIPAY_APP_ID, 63 app_notify_url=None, # 默認回調url 64 # 應用私鑰 65 app_private_key_path=settings.APP_PRIVATE_KEY_PATH, 66 # 支付寶的公鑰, 67 alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH, 68 sign_type="RSA2", # 密碼加密的算法 69 # 開發時屬於調試模式 70 debug = settings.ALIPAY_DEBUG # 默認False 71 ) 72 73 # verify驗證支付結果,布爾值 74 success = alipay.verify(data, signature) 75 76 # 修改狀態,如更改訂單支付時間,支付狀態等 77 if success: 78 # 支付成功 79 order = Order.objects.get(order_number=data.get("out_trade_no")) 80 order.order_status = 1 # 修改訂單狀態 81 order.pay_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 82 order.save() 83 84 return Response({ 85 "length": order.order_course.count(), 86 "paytime": order.pay_time, 87 "price": order.total_price, 88 "info": order.order_desc, 89 }, status=status.HTTP_200_OK) 90 91 return Response({"message": "訂單沒有變化"})
相關settings的配置信息:
1 # 支付寶 2 ALIPAY_APP_ID="20160********971" # 應用ID 3 APLIPAY_APP_NOTIFY_URL = None # 應用回調地址[支付成功之後,支付寶返回結果到哪個地址下面] 4 APP_PRIVATE_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/app_private_key.pem") 5 ALIPAY_PUBLIC_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/alipay_public_key.pem") 6 ALIPAY_DEBUG = False 7 # APIPAY_GATEWAY="https://openapi.alipay.com/gateway.do" #上線使用 8 APIPAY_GATEWAY="https://openapi.alipaydev.com/gateway.do" #開發使用 9 ALIPAY_RETURN_URL = "http://127.0.0.1:8080/success" 10 ALIPAY_NOTIFY_URL = "http://127.0.0.1:8080/success"