天天看點

day88:luffy:支付寶同步結果通知&接收異步支付結果&使用者購買記錄&我的訂單

目錄

1.支付寶同步結果通知

2.使用者購買記錄表

3.接受異步支付結果

4.善後事宜

5.我的訂單

1.支付寶同步結果通知

1.get請求支付寶,支付寶傳回給你的參數

當使用者輸入使用者名和密碼确認支付的時候,支付寶會給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      

這些參數的含義:https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay

2.後端實作處理支付寶同步通知結果的視圖

支付寶将參數傳遞給了vue前端,vue前端要這些參數發送給後端,讓後端去對這些參數做一個校驗。判斷這個url是不是支付寶發過來的

後端校驗支付寶傳回給你的那些參數

payment/urls.py

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參數

前端發送請求 将支付寶發給vue前端的一大堆參數傳遞到後端,後端做完校驗傳回一個成功與否的結果

如果校驗沒有問題 就可以顯示購買成功了。

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);
        })
      },      

2.使用者購買記錄表

當使用者購買成功之後,應該生成使用者記錄,主要用來存支付平台的流水号,有了這個流水号就可以去支付寶查賬單了。以及課程的購買時間和過期時間。

users/models.py

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      

3.接受異步支付結果

payment/views.py

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\')      

4.善後事宜

當一切都完成後,還有幾件事情要做:

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()      

5.我的訂單

1.我的訂單界面-初始化

Myorder.vue

day88:luffy:支付寶同步結果通知&接收異步支付結果&使用者購買記錄&我的訂單
day88:luffy:支付寶同步結果通知&接收異步支付結果&使用者購買記錄&我的訂單
<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/[email protected]"></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
    },      

2.我的訂單頁面-後端接口

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
          

3.我的訂單頁面-前端

1.擷取後端的訂單資料

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>
               

2.我的訂單頁面點選去付款

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);
        })