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);
        })
posted @ 2020-11-12 16:52  iR-Poke  阅读(845)  评论(0编辑  收藏  举报