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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM