• luffy(六)


    一.课程详情页面CourseDetail.vue

    <template>
        <div class="detail">
            <Header/>
            <div class="main">
                <div class="course-info">
                    <div class="wrap-left">
                        <videoPlayer class="video-player vjs-custom-skin"
                                     ref="videoPlayer"
                                     :playsinline="true"
                                     :options="playerOptions"
                                     @play="onPlayerPlay($event)"
                                     @pause="onPlayerPause($event)">
                        </videoPlayer>
                    </div>
                    <div class="wrap-right">
                        <h3 class="course-name">{{course_info.name}}</h3>
                        <p class="data">{{course_info.students}}人在学&nbsp;&nbsp;&nbsp;&nbsp;课程总时长:{{course_info.sections}}课时/{{course_info.pub_sections}}小时&nbsp;&nbsp;&nbsp;&nbsp;难度:{{course_info.level_name}}</p>
                        <div v-if="course_info.active_time>0">
                            <div class="sale-time">
                                <p class="sale-type">{{course_info.discount_type}}</p>
                                <p class="expire">距离结束:仅剩{{day}}天 {{hour}}小时 {{minute}}分 <span
                                        class="second">{{second}}</span> 秒</p>
                            </div>
                            <p class="course-price">
                                <span>活动价</span>
                                <span class="discount">¥{{course_info.real_price}}</span>
                                <span class="original">¥{{course_info.price}}</span>
                            </p>
                        </div>
                        <div v-else class="sale-time">
                            <p class="sale-type">价格 <span class="original_price">¥{{course_info.price}}</span></p>
                            <p class="expire"></p>
                        </div>
                        <div class="buy">
                            <div class="buy-btn">
                                <button class="buy-now">立即购买</button>
                                <button class="free">免费试学</button>
                            </div>
                            <!--<div class="add-cart" @click="add_cart(course_info.id)"><img src="@/assets/img/cart-yellow.svg"-->
                                                                                         <!--alt="">加入购物车-->
                            <!--</div>-->
                        </div>
                    </div>
                </div>
                <div class="course-tab">
                    <ul class="tab-list">
                        <li :class="tabIndex==1?'active':''" @click="tabIndex=1">详情介绍</li>
                        <li :class="tabIndex==2?'active':''" @click="tabIndex=2">课程章节 <span :class="tabIndex!=2?'free':''">(试学)</span>
                        </li>
                        <li :class="tabIndex==3?'active':''" @click="tabIndex=3">用户评论</li>
                        <li :class="tabIndex==4?'active':''" @click="tabIndex=4">常见问题</li>
                    </ul>
                </div>
                <div class="course-content">
                    <div class="course-tab-list">
                        <div class="tab-item" v-if="tabIndex==1">
                            <div class="course-brief" v-html="course_info.brief_text"></div>
                        </div>
                        <div class="tab-item" v-if="tabIndex==2">
                            <div class="tab-item-title">
                                <p class="chapter">课程章节</p>
                                <p class="chapter-length">共{{course_chapters.length}}章 {{course_info.sections}}个课时</p>
                            </div>
                            <div class="chapter-item" v-for="chapter in course_chapters" :key="chapter.name">
                                <p class="chapter-title"><img src="@/assets/img/enum.svg" alt="">第{{chapter.chapter}}章·{{chapter.name}}
                                </p>
                                <ul class="section-list">
                                    <li class="section-item" v-for="section in chapter.coursesections" :key="section.name">
                                        <p class="name"><span class="index">{{chapter.chapter}}-{{section.orders}}</span>
                                            {{section.name}}<span class="free" v-if="section.free_trail">免费</span></p>
                                        <p class="time">{{section.duration}} <img src="@/assets/img/chapter-player.svg"></p>
                                        <button class="try" v-if="section.free_trail">立即试学</button>
                                        <button class="try" v-else>立即购买</button>
                                    </li>
                                </ul>
                            </div>
                        </div>
                        <div class="tab-item" v-if="tabIndex==3">
                            用户评论
                        </div>
                        <div class="tab-item" v-if="tabIndex==4">
                            常见问题
                        </div>
                    </div>
                    <div class="course-side">
                        <div class="teacher-info">
                            <h4 class="side-title"><span>授课老师</span></h4>
                            <div class="teacher-content">
                                <div class="cont1">
                                    <img :src="course_info.teacher.image">
                                    <div class="name">
                                        <p class="teacher-name">{{course_info.teacher.name}}
                                            {{course_info.teacher.title}}</p>
                                        <p class="teacher-title">{{course_info.teacher.signature}}</p>
                                    </div>
                                </div>
                                <p class="narrative">{{course_info.teacher.brief}}</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <Footer/>
        </div>
    </template>
    
    <script>
        import Header from "@/components/Header"
        import Footer from "@/components/Footer"
    
        // 加载组件
        import {videoPlayer} from 'vue-video-player';
    
        export default {
            name: "Detail",
            data() {
                return {
                    tabIndex: 2,   // 当前选项卡显示的下标
                    course_id: 0, // 当前课程信息的ID
                    course_info: {
                        teacher: {},
                    }, // 课程信息
                    course_chapters: [], // 课程的章节课时列表
                    playerOptions: {
                        aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9""4:3")
                        sources: [{ // 播放资源和资源格式
                            type: "video/mp4",
                            src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的视频地址(必填)
                        }],
                    }
                }
            },
            computed: {
                day() {
                    let day = parseInt(this.course_info.active_time / (24 * 3600));
                    if (day < 10) {
                        return '0' + day;
                    } else {
                        return day;
                    }
                },
                hour() {
                    let rest = parseInt(this.course_info.active_time % (24 * 3600));
                    let hours = parseInt(rest / 3600);
                    if (hours < 10) {
                        return '0' + hours;
                    } else {
                        return hours;
                    }
                },
                minute() {
                    let rest = parseInt(this.course_info.active_time % 3600);
                    let minute = parseInt(rest / 60);
                    if (minute < 10) {
                        return '0' + minute;
                    } else {
                        return minute;
                    }
                },
                second() {
                    let second = this.course_info.active_time % 60;
                    if (second < 10) {
                        return '0' + second;
                    } else {
                        return second;
                    }
                }
            },
            created() {
                this.get_course_id();
                this.get_course_data();
                this.get_chapter();
            },
            methods: {
                onPlayerPlay() {
                    // 当视频播放时,执行的方法
                },
                onPlayerPause() {
                    // 当视频暂停播放时,执行的方法
                },
                get_course_id() {
                    // 获取地址栏上面的课程ID
                    this.course_id = this.$route.params.pk;
                    if (this.course_id < 1) {
                        let _this = this;
                        _this.$alert("对不起,当前视频不存在!", "警告", {
                            callback() {
                                _this.$router.go(-1);
                            }
                        });
                    }
                },
                get_course_data() {
                    // ajax请求课程信息
                    this.$axios.get(`${this.$settings.base_url}/course/${this.course_id}/`).then(response => {
                        // window.console.log(response.data);
                        this.course_info = response.data;
                    }).catch(() => {
                        this.$message({
                            message: "对不起,访问页面出错!请联系客服工作人员!"
                        });
                    })
                },
    
                get_chapter() {
                    // 获取当前课程对应的章节课时信息
                    // http://127.0.0.1:8000/course/chapters/?course=(pk)
                    this.$axios.get(`${this.$settings.base_url}/course/chapters/`, {
                        params: {
                            "course": this.course_id,
                        }
                    }).then(response => {
                        this.course_chapters = response.data;
                    }).catch(error => {
                        window.console.log(error.response);
                    })
                },
                // add_cart(course_id) {
                //     // 添加商品到购物车
                //     // 验证用户登录状态,如果登录了则可以添加商品到购物车,如果没有登录则跳转到登录界面,登录完成以后,才能添加商品到购物车
                //     let token = localStorage.token || sessionStorage.token;
                //     if (!token) {
                //         this.$confirm("对不起,您尚未登录,请登录以后再进行购物车").then(() => {
                //             this.$router.push("/login/");
                //         });
                //         return false; // 阻止代码往下执行
                //     }
                //
                //     // 添加商品到购物车,因为购物车接口必须用户是登录的,所以我们要在请求头中设置 jwttoken
                //     this.$axios.post(`${this.$settings.Host}/cart/`, {
                //         "course_id": course_id,
                //     }, {
                //         headers: {
                //             "Authorization": "jwt " + token,
                //         }
                //     }).then(response => {
                //         this.$message({
                //             message: response.data.message,
                //         });
                //         // 购物车中的商品数量
                //         let total = response.data.total;
                //         this.$store.commit("change_total", total)
                //     }).catch(error => {
                //         this.$message({
                //             message: error.response.data
                //         })
                //     })
                // }
            },
            components: {
                Header,
                Footer,
                videoPlayer, // 注册组件
            }
        }
    </script>

    路由router.js

    import CourseDetail from './views/CourseDetail.vue'
    
    {
                path: '/course/detail/:pk',
                name: 'course-detail',
                component: CourseDetail
            },
     
    依赖:在luffycity目录下的命令
    >: cnpm install vue-video-player
    配置:main.js
    // vue-video播放器
    require('video.js/dist/video-js.css');
    require('vue-video-player/src/custom-theme.css');
    import VideoPlayer from 'vue-video-player'
    Vue.use(VideoPlayer);
    资源:图片放置assrts/img文件夹
    """
    enum.svg
    chapter-player.svg
    cart-yellow.svg
    """
    Course.vue中的转跳链接:
     <router-link :to="'/course/detail/'+course.id">{{course.name}}</router-link>

    二.课程详情接口

    路由course/urls.py:

        from django.urls import path, re_path
    
        from . import views
    
        re_path('(?P<pk>d+)/', views.CourseRetrieveAPIView.as_view()),
        path('chapters/', views.ChapterListAPIView.as_view()),

    视图views.py:

    from rest_framework.generics import RetrieveAPIView
    class CourseRetrieveAPIView(RetrieveAPIView):
        queryset = models.Course.objects.filter(is_delete=False, is_show=True)
        serializer_class = serializers.CourseModelSerializer
    
    
    from .filters import ChapterFilterSet
    class ChapterListAPIView(ListAPIView):
        queryset = models.CourseChapter.objects.filter(is_delete=False, is_show=True)
        serializer_class = serializers.CourseChapterModelSerializer
    
        filter_backends = [DjangoFilterBackend]
        # filter_fields = ('course',)
        filter_class = ChapterFilterSet

    序列化类serializers.py:

    class CourseModelSerializer(ModelSerializer):
        teacher = TeacherModelSerializer()
        class Meta:
            model = models.Course
            fields = (
                'id',
                'name',
                'course_img',
                'brief',
                'period',
                'attachment_path',
                'students',
                'sections',
                'pub_sections',
                'price',
                'teacher',
                'section_list',
                'level_name',
            )
    
    class CourseSectionModelSerializer(ModelSerializer):
        class Meta:
            model = models.CourseSection
            fields = ('name', 'section_link', 'name', 'free_trail', 'orders')
    
    class CourseChapterModelSerializer(ModelSerializer):
        coursesections = CourseSectionModelSerializer(many=True)
        class Meta:
            model = models.CourseChapter
            fields = ('course', 'chapter', 'name', 'summary', 'coursesections')

    添加难度字段level_name   =>  models.py/class Course(BaseModel):

     

        @property
        def level_name(self):
            return self.get_level_display()

    三.订单模块

     创建apps/order:

    cd luffyapi/apps
    python ../../manage.py startapp order

    路由:

    主:
    path('order/', include('order.urls')),
    子:
    from django.urls import path
    from . import views
    urlpatterns = [
        path('pay/', views.PayAPIView.as_view()),
        path('success/', views.SuccessAPIView.as_view()),
    ]

    models.py:

    """
    订单:订单号、流水号、价格、用户
    订单详情(自定义关系表):订单、课程
    """
    
    from django.db import models
    from utils.model import BaseModel
    from user.models import User
    from course.models import Course
    
    
    class Order(BaseModel):
        """订单模型"""
        status_choices = (
            (0, '未支付'),
            (1, '已支付'),
            (2, '已取消'),
            (3, '超时取消'),
        )
        pay_choices = (
            (1, '支付宝'),
            (2, '微信支付'),
        )
        subject = models.CharField(max_length=150, verbose_name="订单标题")
        total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0)
        out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True)
        trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号")
        order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
        pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
        pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
        user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING, db_constraint=False,
                                 verbose_name="下单用户")
    
        # 多余字段
        orders = models.IntegerField(verbose_name='显示顺序', default=0)
    
        class Meta:
            db_table = "luffy_order"
            verbose_name = "订单记录"
            verbose_name_plural = "订单记录"
    
        def __str__(self):
            return "%s - ¥%s" % (self.subject, self.total_amount)
    
        @property
        def courses(self):
            data_list = []
            for item in self.order_courses.all():
                data_list.append({
                    "id": item.id,
                    "course_name": item.course.name,
                    "real_price": item.real_price,
                })
    
            return data_list
    
    
    class OrderDetail(BaseModel):
        """订单详情"""
        order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False,
                                  verbose_name="订单")
        course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False,
                                   verbose_name="课程")
        price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
        real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")
    
        class Meta:
            db_table = "luffy_order_detail"
            verbose_name = "订单详情"
            verbose_name_plural = "订单详情"
    
        def __str__(self):
            return "%s订单(%s)" % (self.course.name, self.order.order_number)

    注:数据库迁移

    四.支付宝应用开发

    # 1、在沙箱环境下实名认证:https://openhome.alipay.com/platform/appDaily.htm?tab=info
    
    # 2、电脑网站支付API:https://docs.open.alipay.com/270/105898/
    
    # 3、完成RSA密钥生成:https://docs.open.alipay.com/291/105971
    
    # 4、在开发中心的沙箱应用下设置应用公钥:填入生成的公钥文件中的内容
    
    # 5、Python支付宝开源框架:https://github.com/fzlee/alipay
    # >: pip install python-alipay-sdk --upgrade
    
    # 7、公钥私钥设置
    """
    # alipay_public_key.pem
    -----BEGIN PUBLIC KEY-----
    支付宝公钥
    -----END PUBLIC KEY-----
    
    # app_private_key.pem
    -----BEGIN RSA PRIVATE KEY-----
    用户私钥
    -----END RSA PRIVATE KEY-----
    """
    
    # 8、支付宝链接
    """
    开发:https://openapi.alipay.com/gateway.do
    沙箱:https://openapi.alipaydev.com/gateway.do
    """

    RSA:

    支付宝公钥:

    前台后台支付宝交互原理图:

    沙箱测试账号:

    五.alipay二次封装包

    依赖
    >: pip install python-alipay-sdk --upgrade
    结构
    libs
       ├── iPay # aliapy二次封装包
       │   ├── __init__.py # 包文件
       │   ├── keys # 密钥文件夹
       │   │   ├── alipay_public_key.pem # 支付宝公钥
       │   │   └── app_private_key.pem # 应用私钥
       └── └── settings.py # 应用配置  
    setting.py
    import os
    # 支付宝应用id
    APP_ID = '2016093000631831'
    # 默认异步回调的地址,通常设置None就行
    APP_NOTIFY_URL = None
    # 应用私钥文件路径
    APP_PRIVATE_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'app_private_key.pem')
    # 支付宝公钥文件路径
    ALIPAY_PUBLIC_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'alipay_public_key.pem')
    # 签名方式
    SIGN_TYPE = 'RSA2'
    # 是否是测试环境
    DEBUG = True
    __init__.py
    from alipay import AliPay
    from .settings import *
    # 对外提供,放到自己的dev配置文件中
    # from .settings import RETURN_URL, NOTIFY_URL
    # 对外提供支付对象
    alipay = AliPay(
        appid=APP_ID,
        app_notify_url=APP_NOTIFY_URL,
        app_private_key_path=APP_PRIVATE_KEY_PATH,
        alipay_public_key_path=ALIPAY_PUBLIC_KEY_PATH,
        sign_type=SIGN_TYPE,
        debug=DEBUG
    )
    alipay_public_key.pem
    -----BEGIN PUBLIC KEY-----
    支付宝公钥
    -----END PUBLIC KEY-----
    app_private_key.pem
    -----BEGIN RSA PRIVATE KEY-----
    应用私钥
    -----END RSA PRIVATE KEY-----
    补充:dev.py
    # 上线后必须换成官网地址
    # 同步回调的接口(get),前后台分离时一般设置前台页面url
    RETURN_URL = 'http://127.0.0.1:8080/pay/success'
    # 异步回调的接口(post),一定设置为后台服务器接口
    NOTIFY_URL = 'http://127.0.0.1:8000/order/success/'

    六.订单接口

    订单视图views.py:

    # 1)生成订单
    # 2)生成支付链接
    # 3)第三方支付
    # 4)修改订单状态
    
    import time
    from rest_framework.views import APIView
    from utils.response import APIResponse
    from libs.iPay import alipay
    from . import authentications, serializers
    from rest_framework.permissions import IsAuthenticated
    from django.conf import settings
    # 获取前台 商品名、价格,产生 订单、支付链接
    class PayAPIView(APIView):
        authentication_classes = [authentications.JWTAuthentication]
        permission_classes = [IsAuthenticated]
        def post(self, request, *args, **kwargs):
            # 前台提供:商品名、总价、支付方式
            request_data = request.data
            # 后台产生:订单号、用户
            out_trade_no = '%d' % time.time() * 2
            request_data['out_trade_no'] = out_trade_no
            request_data['user'] = request.user.id
    
            # 反序列化数据,用于订单生成前的校验
            order_ser = serializers.OrderModelSerializer(data=request_data)
            if order_ser.is_valid():
                # 生成订单,订单默认状态为:未支付
                order = order_ser.save()
                # 支付链接的参数
                order_string = alipay.api_alipay_trade_page_pay(
                    subject=order.subject,
                    out_trade_no=order.out_trade_no,
                    total_amount='%.2f' % order.total_amount,
                    return_url=settings.RETURN_URL,
                    notify_url=settings.NOTIFY_URL
                )
                # 形成支付链接:alipay._gateway根据字符环境DEBUG配置信息,决定是沙箱还是真实支付环境
                pay_url = '%s?%s' % (alipay._gateway, order_string)
                return APIResponse(0, 'ok', pay_url=pay_url)
    
    
            return APIResponse(1, 'no ok', results=order_ser.errors)

    用户校验需要认证authentications.py:

    import jwt
    from rest_framework.exceptions import AuthenticationFailed
    from rest_framework_jwt.authentication import jwt_decode_handler
    from rest_framework_jwt.authentication import get_authorization_header
    from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
    
    class JWTAuthentication(BaseJSONWebTokenAuthentication):
        def authenticate(self, request):
            # jwt_value = get_authorization_header(request)
            jwt_value = request.META.get('HTTP_AUTHORIZATION', b'')
    
            if not jwt_value:
                raise AuthenticationFailed('Authorization 字段是必须的')
            try:
                payload = jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('签名过期')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('非法用户')
            user = self.authenticate_credentials(payload)
    
            return user, jwt_value

    序列化类serializers.py:

    from rest_framework import serializers
    from . import models
    class OrderModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Order
            fields = ('subject', 'total_amount', 'out_trade_no', 'pay_type', 'user')
            extra_kwargs = {
                'pay_type': {
                    'required': True
                },
                'total_amount': {
                    'required': True
                },
            }
            # 如果需要处理订单详情,前台一定要提供 课程主键(一个或多个)
            # 需要重写create方法:1)产生Order表对象  2)产生OrderDetail表对象 => 购物车逻辑
            # 需求可拓展:UserCourse user course

    七.前台生成订单

    Course.vue链接跳转支付:

      <span class="buy-now" @click="pay_course(course)">立即购买</span>
    
    
    ......
    
    methods: {
                // 购买课程
                pay_course(course) {
                    // 判断登录状态
                    let token = this.$cookies.get('token');
                    if (!token) {
                        this.$message.error('请先登录');
                        return
                    }
                    this.$axios({
                        url: this.$settings.base_url + '/order/pay/',
                        method: 'post',
                        data: {
                            'subject': course.name,
                            'total_amount': course.price,
                            // 如果有支付页面:1 支付宝  2 微信
                            'pay_type': 1,
                        },
                        headers: {
                            Authorization: token
                        }
                    }).then(response => {
                        // console.log(response.data)
                        if (response.data.status == 0) {
                            location.href = response.data.pay_url;
                        } else {
                            this.$message({
                                message: '生成订单失败'
                            })
                        }
                    }).catch(() => {
                        this.$message({
                            message: '生成订单失败'
                        })
                    })
                },
    ......

    八.支付完成后同步回调链接给前台渲染

     router.js:

    import PaySuccess from './views/PaySuccess.vue'
    
    Vue.use(Router);
    
     {
                path: '/pay/success',
                name: 'pay-success',
                component: PaySuccess
            },
      

    PaySuccess.vue:

    <template>
        <div class="pay-success">
            <Header/>
            <div class="main">
                <div class="title">
                    <div class="success-tips">
                        <p class="tips">您已成功购买 1 门课程!</p>
                    </div>
                </div>
                <div class="order-info">
                    <p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p>
                    <p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p>
                    <p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p>
                </div>
                <div class="study">
                    <span>立即学习</span>
                </div>
            </div>
            <Footer/>
        </div>
    </template>
    
    <script>
        import Header from "@/components/Header"
        import Footer from "@/components/Footer"
    
        export default {
            name: "Success",
            data() {
                return {
                    result: {},
                };
            },
            created() {
                // 判断登录状态
                let token = this.$cookies.get('token');
                if (!token) {
                    this.$message.error('非法请求');
                    this.$router.go(-1)
                }
    
    
                localStorage.this_nav = '/';
                if (!location.search.length) return;
                let params = location.search.substring(1);
                let items = params.length ? params.split('&') : [];
                //逐个将每一项添加到args对象中
                for (let i = 0; i < items.length; i++) {
                    let k_v = items[i].split('=');
                    //解码操作,因为查询字符串经过编码的
                    let k = decodeURIComponent(k_v[0]);
                    let v = decodeURIComponent(k_v[1]);
                    this.result[k] = v;
                    // this.result[k_v[0]] = k_v[1];
                }
                // console.log(this.result);
    
                // 把地址栏上面的支付结果,转发给后端
                this.$axios({
                    url: this.$settings.base_url + '/order/success/' + location.search,
                    method: 'patch',
                    headers: {
                        Authorization: token
                    }
                }).then(response => {
                    console.log(response.data);
                }).catch(() => {
                    console.log('支付结果同步失败');
                })
            },
            components: {
                Header,
                Footer,
            }
        }
    </script>
    
    <style scoped>
    
        .main {
            padding: 60px 0;
            margin: 0 auto;
             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;
             60px;
            height: 60px;
            margin-right: 40px;
        }
    
        .title .success-tips {
            box-sizing: border-box;
        }
    
        .title .tips {
            font-size: 26px;
            color: #000;
        }
    
    
        .info span {
            color: #ec6730;
        }
    
        .order-info {
            padding: 25px 48px;
            padding-bottom: 15px;
            border-bottom: 1px solid #f2f2f2;
        }
    
        .order-info p {
            display: -ms-flexbox;
            display: flex;
            margin-bottom: 10px;
            font-size: 16px;
        }
    
        .order-info p b {
            font-weight: 400;
            color: #9d9d9d;
            white-space: nowrap;
        }
    
        .study {
            padding: 25px 40px;
        }
    
        .study span {
            display: block;
             140px;
            height: 42px;
            text-align: center;
            line-height: 42px;
            cursor: pointer;
            background: #ffc210;
            border-radius: 6px;
            font-size: 16px;
            color: #fff;
        }
    </style>

    页面如图:

    九.同步回调到后端

    视图order/views.py:

    from . import models
    from utils.logging import logger
    from rest_framework.response import Response
    class SuccessAPIView(APIView):
        # 不能认证,别人支付宝异步回调就进不来了
        # authentication_classes = [authentications.JWTAuthentication]
        # permission_classes = [IsAuthenticated]
        def patch(self, request, *args, **kwargs):
            # 默认是QueryDict类型,不能使用pop方法
            request_data = request.query_params.dict()
            # 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验
            sign = request_data.pop('sign')
            result = alipay.verify(request_data, sign)
            if result:  # 同步回调:修改订单状态
                try:
                    out_trade_no = request_data.get('out_trade_no')
                    order = models.Order.objects.get(out_trade_no=out_trade_no)
                    if order.order_status != 1:
                        order.order_status = 1
                        order.save()
                except:
                    pass
                return APIResponse(0, '支付成功')
            return APIResponse(1, '支付失败')
    
        # 支付宝异步回调
        def post(self, request, *args, **kwargs):
            # 默认是QueryDict类型,不能使用pop方法
            request_data = request.data.dict()
            # 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验
            sign = request_data.pop('sign')
            result = alipay.verify(request_data, sign)
            # 异步回调:修改订单状态
            if result and request_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED" ):
                out_trade_no = request_data.get('out_trade_no')
                logger.critical('%s支付成功' % out_trade_no)
                try:
                    order = models.Order.objects.get(out_trade_no=out_trade_no)
                    if order.order_status != 1:
                        order.order_status = 1
                        order.save()
                except:
                    pass
                # 支付宝八次异步通知,订单成功一定要返回 success
                return Response('success')
            return Response('failed')
  • 相关阅读:
    android手机rootROM下载地址
    mysql alter 语句用法,添加、修改、删除字段等
    java比较两个日期大小
    eclipse设置全局编码为UTF-8的方法
    Spring MVC 异步处理请求,提高程序性能
    ELK(ElasticSearch, Logstash, Kibana)搭建实时日志分析平台
    maven之发布项目到nexus【clean deploy命令】
    nexus-3本地下载jar的settipng.xml配置
    windows开启3306端口并用可视化工具访问远程mysql(授权访问)
    mysql 列转行,合并字段
  • 原文地址:https://www.cnblogs.com/sima-3/p/11556399.html
Copyright © 2020-2023  润新知