當用戶輸入用戶名和密碼確認支付的時候,支付寶會給vue前端回復一個url,這個url很長,后面包含了很多參數,參數如下所示:
http://www.luffycity.cn:8080/payments/result? charset=utf8& out_trade_no=20190929151453000001000020& method=alipay.trade.page.pay.return& total_amount=310.00& sign=kebIZBI%2FpCNXmCivfJPPw21gcobulPZoSh%2BXiHR8l6cgexQi2STG4AZgr%2FEUhvc5kEMacJLvCmBaw1Wqo4WK3sPzbUaPmzq3NshUNzYK2lWTsmOauidNxlk1bK0Q%2FANBfQUkmj6TQjyB5T9QqEnS80KFsDrGrasU%2B%2Fz9W%2FjOCLrSji6TnKhRkI9pqBMdw823ABU75b7zOtXzcXKduO%2B6vsXVvluMzedss9dHs1celxPAWQV9jcKjzq%2F1bPbZcmgAGNQQecoJ%2BFSc3uTmTk24uV39PM54LIlg8aeRlkPNjvhBkJh%2FG0%2BURNDdG2593IFIF%2BUqoU%2F7ixm19dX222GCWg%3D%3D& trade_no=2019092922001439881000120282& auth_app_id=2016091600523592& version=1.0& app_id=2016091600523592& sign_type=RSA2& seller_id=2088102175868026& timestamp=2019-09-29%2015%3A15%3A53
支付寶將參數傳遞給了vue前端,vue前端要這些參數發送給后端,讓后端去對這些參數做一個校驗。判斷這個url是不是支付寶發過來的
from django.urls import path,re_path from . import views urlpatterns = [ path('result/',views.AlipayResultView.as_view(),) ]
payment/view.py
class AlipayResultView(APIView): permission_classes = [IsAuthenticated, ] def get(self,request): # 1.創建alipay對象 alipay = AliPay( appid=settings.ALIAPY_CONFIG['appid'], app_notify_url=None, # 默認回調url app_private_key_string=open(settings.ALIAPY_CONFIG['app_private_key_path']).read(), # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, alipay_public_key_string=open(settings.ALIAPY_CONFIG['alipay_public_key_path']).read(), sign_type=settings.ALIAPY_CONFIG['sign_type'], # RSA 或者 RSA2 debug=settings.ALIAPY_CONFIG['debug'], # 默認False ) # 2.校驗支付寶響應數據 data = request.query_params.dict() # 獲取那一大堆的參數 out_trade_no = data.get('out_trade_no') # 獲取商戶訂單號 sign = data.pop('sign') # 獲取簽名 success = alipay.verify(data,sign) # 通過參數和簽名來驗證那一堆參數是不是支付寶發過來的 if not success: logger.error('%s,支付寶響應數據校驗失敗' % out_trade_no) return Response('支付寶響應數據校驗失敗',status=status.HTTP_400_BAD_REQUEST) # 響應結果 return Response({'msg':'ok','data':res_data})
3.vue前端發送請求驗證get參數
如果校驗沒有問題 就可以顯示購買成功了。
Success.vue
created(){ // 把地址欄上面的支付結果,轉發給后端 this.send_alipay_params(); methods:{ send_alipay_params(){ let token = localStorage.token || sessionStorage.token; this.$axios.get(`${this.$settings.Host}/payment/result/${location.search}`,{ headers:{ 'Authorization':'jwt ' + token } }).then((res)=>{ // 如果后端驗證這些參數沒有問題 購買成功頁面需要的參數就可以傳遞過來了 this.pay_time = res.data.data.pay_time; // 支付時間 this.course_list = res.data.data.course_list; // 購買課程列表 this.total_real_price = res.data.data.total_real_price; // 課程總真實價格 }).catch((error)=>{ this.$message.error(error.response.data.msg); }) },
class UserCourse(BaseModel): """用戶的課程購買記錄""" pay_choices = ( (1, '用戶購買'), (2, '免費活動'), (3, '活動贈品'), (4, '系統贈送'), ) user = models.ForeignKey(User, related_name='user_courses', on_delete=models.DO_NOTHING, verbose_name="用戶") course = models.ForeignKey(Course, related_name='course_users', on_delete=models.DO_NOTHING, verbose_name="課程") trade_no = models.CharField(max_length=128, null=True, blank=True, verbose_name="支付平台的流水號", help_text="將來依靠流水號到支付平台查賬單") buy_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="購買方式") pay_time = models.DateTimeField(null=True, blank=True, verbose_name="購買時間") out_time = models.DateTimeField(null=True, blank=True, verbose_name="過期時間") # null表示永不過期 class Meta: db_table = 'ly_user_course' verbose_name = '課程購買記錄' verbose_name_plural = verbose_name
class AlipayResultView(APIView): permission_classes = [IsAuthenticated, ] def post(self,request): # 創建alipay對象 alipay = AliPay( appid=settings.ALIAPY_CONFIG['appid'], app_notify_url=None, # 默認回調url app_private_key_string=open(settings.ALIAPY_CONFIG['app_private_key_path']).read(), # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, alipay_public_key_string=open(settings.ALIAPY_CONFIG['alipay_public_key_path']).read(), sign_type=settings.ALIAPY_CONFIG['sign_type'], # RSA 或者 RSA2 debug=settings.ALIAPY_CONFIG['debug'], # 默認False ) # 校驗支付寶響應數據 data = request.data.dict() sign = data.pop('sign') success = alipay.verify(data,sign) if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"): self.change_order_status(data) return Response('success')
1.修改訂單狀態
2.扣除優惠劵 積分
3.清空購物車數據和結算頁面全部數據
4.將相關信息存到用戶購買記錄表中
payment/views.py
class AlipayResultView(APIView): def change_order_status(self,data): # 修改訂單狀態 with transaction.atomic(): out_trade_no = data.get('out_trade_no') trade_no = data.get('trade_no') # A.修改訂單狀態 # 1.獲取當前訂單號的訂單對象 order_obj = Order.objects.get(order_number=out_trade_no) # 2.將當前訂單的訂單狀態改為1:已支付 order_obj.order_status = 1 # 3.保存訂單信息 order_obj.save() # B.修改優惠券的使用狀態 if order_obj.coupon > 0: # 如果用戶使用了優惠劵 # 1.獲取用戶使用的那張優惠劵對象 user_coupon_obj = UserCoupon.objects.get(is_use=False, id=order_obj.coupon) # 2.將用戶使用的那張優惠劵的狀態由未使用改為已使用 user_coupon_obj.is_use = True # 3.保存用戶的優惠劵的信息 user_coupon_obj.save() # C.支付成功,用戶積分應該對應的扣除 # 1.查詢當前訂單使用了多少積分 use_credit = order_obj.credit # 2.查詢到用戶的總積分數減去使用的積分數得到用戶的剩余積分數 self.request.user.credit -= use_credit # 3.保存用戶的積分信息 self.request.user.save() # D.保存支付寶的交易流水號(購買記錄表) # 1.通過訂單對象反向查詢到所有的訂單詳情對象 order_detail_objs = order_obj.order_courses.all() # 2.獲取當前時間 now = datetime.datetime.now() # 購買成功 從redis中將課程的選中狀態刪除掉 conn = get_redis_connection('cart') pipe = conn.pipeline() pipe.delete('selected_cart_%s' % self.request.user.id) # 需要給購買成功頁面(Success.vue返回的數據) res_data = { 'pay_time': now, 'course_list': [], 'total_real_price': order_obj.real_price, } for order_detail in order_detail_objs: # 購買成功 課程學習的學生數+1 course = order_detail.course course.students += 1 course.save() # 購買成功的課程應該顯示課程列表的每個課程 res_data['course_list'].append(course.name) # 從課程詳情頁獲取當前課程的有效期數值 expire_id = order_detail.expire if expire_id > 0: # 如果不是永久有效 # 根據expire_id查詢到課程有效期model對象 expire_obj = CourseExpire.objects.get(id=expire_id) # 查詢到課程的有效期(天數) expire_time = expire_obj.expire_time # 計算課程的過期時間 out_time = now + datetime.timedelta(days=expire_time) else: # 如果是永久有效,就沒有過期時間 out_time = None # 一切處理完畢,將相關信息存到用戶購買記錄表中 UserCourse.objects.create(**{ 'user':self.request.user, 'course':course, 'trade_no':trade_no, 'buy_type':1, 'pay_time':now, 'out_time':out_time, }) # 購物車redis數據刪除 pipe.hdel('cart_%s' % self.request.user.id, course.id) pipe.execute() return res_data
order/serializers.py
def create: order_obj.coupon = coupon_id order_obj.credit = credit order_obj.save()
Myorder.vue

<template> <div class="user-order"> <Vheader/> <div class="main"> <div class="banner"></div> <div class="profile"> <div class="profile-info"> <div class="avatar"><img class="newImg" width="100%" alt="" src="../../static/img/logo@2x.png"></div> <span class="user-name">吳某某</span> <span class="user-job">北京市 | 程序員</span> </div> <ul class="my-item"> <li>我的賬戶</li> <li class="active">我的訂單</li> <li>個人資料</li> <li>賬號安全</li> </ul> </div> <div class="user-data"> <ul class="nav"> <li class="order-info">訂單</li> <li class="course-expire">有效期</li> <li class="course-price">課程價格</li> <li class="real-price">實付金額</li> <li class="order-status">交易狀態</li> <li class="order-do">交易操作</li> </ul> <div class="my-order-item" v-for="(order_obj,index) in order_list" :key="index"> <div class="user-data-header"> <span class="order-time">xxxx</span> <span class="order-num">訂單號: <span class="my-older-number">xxxx</span> </span> </div> <ul class="nav user-data-list" v-for="(course_obj,course_index) in order_obj.order_detail_data"> <li class="order-info"> <img :src="course_obj.course_img" alt=""> <div class="order-info-title"> <p class="course-title">xxxx</p> <p class="price-service">xxxx</p> </div> </li> <li class="course-expire">xxxx</li> <li class="course-price">xxxx</li> <li class="real-price">xxxx</li> <li class="order-status">xxxx</li> <li class="order-do"> <span class="btn btn2" v-if="order_obj.get_order_status_display==='已支付'">去學習</span> <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='未支付'" @click="go_pay(order_obj.order_number)">去付款</span> <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='超時取消'">超時取消</span> <span class="btn btn2" v-else>已取消</span> </li> </ul> </div> </div> </div> <Footer/> </div> </template> <script> import Vheader from "./common/Vheader" import Footer from "./common/Footer" export default{ name:"Myorder", data(){ return { }; }, created(){ }, methods:{ }, components:{ Vheader, Footer, } } </script> <style scoped> .main .banner{ width: 100%; height: 324px; background: url(../../static/img/my_bkging.0648ebe.png) no-repeat; background-size: cover; z-index: 1; } .profile{ width: 1200px; margin: 0 auto; } .profile-info{ text-align: center; margin-top: -80px; } .avatar{ width: 120px; height: 120px; border-radius: 60px; overflow: hidden; margin: 0 auto; } .user-name{ display: block; font-size: 24px; color: #4a4a4a; margin-top: 14px; } .user-job{ display: block; font-size: 11px; color: #9b9b9b; } .my-item{ list-style: none; line-height: 1.42857143; color: #333; width: 474px; height: 31px; display: -ms-flexbox; display: flex; cursor: pointer; margin: 41px auto 0; -ms-flex-pack: justify; justify-content: space-between; } .my-item .active{ border-bottom: 1px solid #000; } .user-data{ width: 1200px; height: auto; margin: 0 auto; padding-top: 30px; border-top: 1px solid #e8e8e8; margin-bottom: 63px; } .nav{ width: 100%; height: 60px; background: #e9e9e9; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; } .nav li{ margin-left: 20px; margin-right: 28px; height: 60px; line-height: 60px; list-style: none; font-size: 13px; color: #333; border-bottom: 1px solid #e9e9e9; width: 160px; } .nav .order-info{ width: 325px; } .nav .course-expire{ width: 60px; } .nav .course-price{ width: 130px; } .user-data-header{ display: flex; height: 44px; color: #4a4a4a; font-size: 14px; background: #f3f3f3; -ms-flex-align: center; align-items: center; } .order-time{ font-size: 12px; display: inline-block; margin-left: 20px; } .order-num{ font-size: 12px; display: inline-block; margin-left: 29px; } .user-data-list{ height: 100%; display: flex; } .user-data-list{ background: none; } .user-data-list li{ height: 60px; line-height: 60px; } .user-data-list .order-info{ display: flex; align-items: center; margin-right: 28px; } .user-data-list .order-info img{ max-width: 100px; max-height: 75px; margin-right: 22px; } .course-title{ width: 203px; font-size: 13px; color: #333; line-height: 5px; margin-top: -10px; } .order-info-title .price-service{ line-height: 18px; } .price-service{ font-size: 12px; color: #fa6240; padding: 0 5px; border: 1px solid #fa6240; border-radius: 4px; margin-top: 4px; position: absolute; } .order-info-title{ margin-top: -10px; } .user-data-list .course-expire{ font-size: 12px; color: #ff5502; width: 60px; text-align: center; } .btn { width: 100px; height: 32px; font-size: 14px; color: #fff; background: #ffc210; border-radius: 4px; border: none; outline: none; transition: all .25s ease; display: inline-block; line-height: 32px; text-align: center; cursor: pointer; } </style>
index.js
{ path: '/myorder/', component: Myorder },
users/urls.py
urlpatterns = [ path(r'myorder/', views.MyOrderView.as_view()), ]
users/views.py
class MyOrderView(ListAPIView): permission_classes = [IsAuthenticated, ] serializer_class = MyOrderModelSerializer def get_queryset(self): return Order.objects.filter(user=self.request.user)
user/serializers.py
class MyOrderModelSerializer(serializers.ModelSerializer): class Meta: model = Order fields = ['id', 'order_number' ,'pay_time', 'get_order_status_display', 'order_detail_data']
order/models.py
在我的訂單頁面中,需要展示一些數據
class Order(BaseModel): def order_detail_data(self): # 獲取所有課程詳情對象 order_detail_objs = self.order_courses.all() data_list = [] for order_detail in order_detail_objs: expire_id = order_detail.expire # 根據有效期來決定expire_text返回什么 if expire_id > 0: expire_obj = CourseExpire.objects.get(id=expire_id) expire_text = expire_obj.expire_text else: expire_text = '永久有效' # 每個課程應該包含的字段 order_dict = { 'course_img':contains.SERVER_ADDR + order_detail.course.course_img.url, 'course_name':order_detail.course.name, 'expire_text':expire_text, 'price':order_detail.price, 'real_price': self.real_price, 'discount_name':order_detail.discount_name, } # 將每個課程的詳情信息添加到一個列表里返回給前端 data_list.append(order_dict) return data_list
Myorder.vue
// js get_order_data(){ // 檢查當前訪問者是否登錄了! let token = localStorage.token || sessionStorage.token; this.$axios.get(`${this.$settings.Host}/users/myorder/`,{ headers:{ 'Authorization':'jwt ' + token } }).then((res)=>{ this.order_list = res.data; }).catch((error)=>{ })
<!-- html --> <div class="my-order-item" v-for="(order_obj,index) in order_list" :key="index"> <div class="user-data-header"> <span class="order-time">{{order_obj.pay_time.replace('T', ' ')}}</span> <span class="order-num">訂單號: <span class="my-older-number">{{order_obj.order_number}}</span> </span> </div> <ul class="nav user-data-list" v-for="(course_obj,course_index) in order_obj.order_detail_data"> <li class="order-info"> <img :src="course_obj.course_img" alt=""> <div class="order-info-title"> <p class="course-title">{{course_obj.course_name}}</p> <p class="price-service">{{course_obj.discount_name}}</p> </div> </li> <li class="course-expire">{{course_obj.expire_text}}</li> <li class="course-price">{{course_obj.price}}</li> <li class="real-price">{{course_obj.real_price}}</li> <li class="order-status">{{order_obj.get_order_status_display}}</li> <li class="order-do"> <span class="btn btn2" v-if="order_obj.get_order_status_display==='已支付'">去學習</span> <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='未支付'" @click="go_pay(order_obj.order_number)">去付款</span> <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='超時取消'">超時取消</span> <span class="btn btn2" v-else>已取消</span> </li> </ul> </div>
Myorder.vue
<!-- html --> <span class="btn btn2" v-else-if="order_obj.get_order_status_display==='未支付'" @click="go_pay(order_obj.order_number)">去付款</span>
// js go_pay(order_number){ let token = localStorage.token || sessionStorage.token; this.$axios.get(`${this.$settings.Host}/payment/alipay/?order_number=${order_number}`,{ headers:{ 'Authorization':'jwt ' + token } }).then((res)=>{ location.href = res.data.url; }).catch((error)=>{ this.$message.error(error.response.data.msg); })