赞
踩
# 沙箱环境:登录 - 支付宝
# 支付宝开放平台文档 下载软件,生成秘钥(公钥私钥)
# 取出公钥,配置再支付宝平台,它会生成一个支付宝公钥
代码:新建文件夹
- # pip install python-alipay-sdk
-
-
- from alipay import AliPay
-
-
- # app_private_key_string = open("/path/to/your/private/key.pem").read()
- # alipay_public_key_string = open("/path/to/alipay/public/key.pem").read()
-
- app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
- MIIEpAIBAAKCAQEApkFnp38PSslhn4wvRarL40+WtVpan6tVT55FWzAzLfQ8PfqZE2qW7mCJy1/zM07+S5L+X0pC+F7BlT+0z4rlxraG5D23khWfHb4x+uDi0Wd94eaVB0PzQXCYYBChuORvNN5YLTspeP7rNQ2OcdpGx2KUzo9w59dVW+AweblFl7GqPkRSC0P5XbkJtwnEAd0Nc08HzJR0OWZT9HRFOA7coaude9uSwDsXmsdkSNGsGDcaGEY6aGk2Tj9Fyni9LJJcOXwGrUuGDpdcP2tYePrFUGMDml/feFR9QGUbExWh/ZNpb9lKLwOtXOhQj9slilp+YJek/rLlSgW9K1WrYpuuBwIDAQABAoIBADN1mRzKAjS2wmW84UDiDbus/cviTJyRTpWXOoZwE9dMen0AnPLakh70eJIff8pI0AMaW2upM7NmuOp2ToPSzS5FftkUlUY9NQPiw9uQUgRY0Sjj0wrtqFSAAlnxq+zrn9QwYgCWCE8wMCM6r/Vjh3bdd4u78EmCaCRI7xguFXFPB9NY9oCFiwsTFJtXPZo+DSIQjqIDnh8YtV3NzrA3Ln3DsKMAr+vnPMeljAL5US6Rt5tOQY0qa9bUi6RqQwTLADcANd49YOvlr7OLjwi5jkeJ16j1nKgpasAIg9V+rF085u+PEKkLL3WJHwmhiIxrA11bxCnm1fRdfHbyWZKY9yECgYEA0k3wS7A7rzhtpznc5ZfVGWIEQ5IahRkqVGbc6wFIXKXnypOtGKAFiRmEPwm9xRurlo8TJ1bjK4D4ljEANJhX2NnIZfK3YDdNEN6D4GnEvPFR641M8M47Z7ItvdRKre8P2/ThXhkMnq5/1y7e87xvE6/a1Tr44+hclHvQWPRrsPcCgYEAymFEy0/2xAX+si4S8pzrgLfBg3Ehjan8T6JZZNCKhR7b/IT+3/AWJimzEL6jYQMlhy7QzZbRb1ssIIzqPFcFQYjQE0n6fswJuN4+jayW9Jtjvar/zhHFjW3EODR5yEDTvS3CHqQLeG3ce+srMJSxVDJR2V9ortFeb+VPbWZ+t3ECgYEAxPYvvoNwcpuzvvGnW/RGpb4x5iL46Xz3MxMfho2t+u961jRW4oBEjvGx9OQnsmpG2vxm4Oo0WnMw3mFIIvonFDZrxGd8rQU+DTWJZ21Hz/lnUugEjmdoJacvxeEEjEAgp02CoQFu21Ls8li4gKgTk+mYVyojHjhqNLp9GELadWMCgYEAi0RSXgLCEnT5p03zdgcsPOC3ByfT6jO+0GItWCX2HNN2mRhAeIQ0CcEKW4yEy56ptZQu1jtiFlpMTH4MNse/czCd15hCC/2G9zPhIgdRvjQsd/nznLA4HTIbJH5gC8EotHeHrSRATHh1kMTtbLn2KbWTA54XYK3tad0IQoWUz9ECgYBBc+kCJsb0shaRIRP5nmeb5tZCH7Yq9DgriwcIlK74cxW9fvL94n91Y6M8vSZ6MC7vF5wmcXMj3K1of04qizKbbKxCCYeOIHhTh0dB+ualDxu5WBG/6mLWR3HVHazqRVri9qoZ/3zNDx5CRNi81VpzOPmVtuos5d9sZZaNR3lkRQ==
- -----END RSA PRIVATE KEY-----
- """
-
- alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwU9mMZHHlQPE9FcxVtXOXhbWCtuDZLJRVCiofdbTVmRXrx47yGniPehwcKIsqhzhEaXBG2QhpIZUL8YsCav0mkrppoRvWOytuGyxNRESo8I6DWRs0aCq6P3AuiD9kSXET4dpAuRYT/+JrMXIZTycEts6vYYNAT9QivXJoa2FmiCQBAL3HP7F36pby9VstObilxXQcoBBJwEYGf2TK6moFFZ1dkloRr5Cfk/G82DpuVfrt1gr4OuIDWtcE3MZTrvDgTqtkRuwGF76FY3+8xUCUbJs1dL5cXYN7/b3jPcXVcdKXFj4WrOQd42ofE1BJWMxBW7L3Qlxue1vy+NGx/CuKwIDAQAB
- -----END PUBLIC KEY-----
- """
-
- alipay = AliPay(
- appid="2016092000554611",
- app_notify_url='http://127.0.0.1:8000/home/', # the default notify path
- app_private_key_string=app_private_key_string,
- # alipay public key, do not use your own public key!
- alipay_public_key_string=alipay_public_key_string,
- sign_type="RSA2", # RSA or RSA2
- debug=True # False by default
- )
-
-
- alipay_url='https://openapi.alipaydev.com/gateway.do?'
- order_string = alipay.api_alipay_trade_page_pay (
- out_trade_no="20161112www4334",
- total_amount=9999,
- subject='韩红版充气娃娃',
- return_url="https://www.luffycity.com/free-course",
- notify_url="https://www.luffycity.com/free-course"
- )
- print(alipay_url+order_string)
- al_pay
- -pem
- -__init__.py
- -pay.py
- -setting.py
-
- #__init__.py
- from .pay import alipay,gateway
-
- #pay.py
- from alipay import AliPay
- from . import setting
- alipay = AliPay(
- appid=setting.APPID,
- app_notify_url=None, # the default notify path
- app_private_key_string=setting.APP_PRIVATE_KEY_STRING,
- # alipay public key, do not use your own public key!
- alipay_public_key_string=setting.ALIPAY_PUBLIC_KEY_STRING,
- sign_type=setting.SIGN_TYPE, # RSA or RSA2
- debug=setting.DEBUG # False by default
- )
- gateway=setting.GATEWAY
-
-
- # setting.py
- import os
- APPID="2016092000554611"
- APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','private_key.pem')).read()
- ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','al_public_key.pem')).read()
- SIGN_TYPE='RSA2'
- DEBUG=True
- GATEWAY='https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'
三 订单模块与表分析
- # 订单表分析
- -订单表
- -订单详情
-
- from django.db import models
- from django.db import models
- from user.models import User
- from course.models import Course
- # 订单表
- class Order(models.Model):
- """订单模型"""
- 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="支付时间")
- # 一个用户可以下多个订单,一个订单只属于一个用户,一对多的关系,关联字段写在多个一方,写在order方
- user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户")
- created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
- updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')
- 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(models.Model):
- """订单详情"""
- 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.SET_NULL, db_constraint=False, verbose_name="课程",null=True)
- 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):
- try:
- return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
- except:
- return super().__str__()
4 支付接口
分析:
- # 1 前端传什么格式数据
- -{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,}
-
- # 2 后端接到数据要校验
- -course:[1,2,3]===》course:[obj1,obj2,obj3]
- -在序列化类中: course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
- # 3 在序列化类的validate中处理一堆逻辑
- '''
- # 1)订单总价校验:
- -一个个课程价格取出来累加看是否等于传入的总价格
- # 2)生成订单号
- -通过uuid生成
- # 3)支付用户:request.user
- -通过视图和序列化类之间的桥梁context对象传递
- -重新视图类中的create方法,把request对象放入context
- -self.context.get('request').user
- # 4)支付链接生成
- -导入封装的支付宝支付,生成
- order_string = alipay.api_alipay_trade_page_pay (
- out_trade_no=out_trade_no,
- total_amount=total_amout,
- subject=subject,
- return_url=settings.RETURN_URL, # get回调,前台地址
- notify_url=settings.NOTIFY_URL # post回调,后台地址
- )
- # 5)入库(两个表)的信息准备
- -把user放入attrs中
- -把订单号,放入attrs中
- attrs['user']=user
- attrs['out_trade_no']=out_trade_no
- self.context['pay_url']=pay_url
- '''
-
- # 4 重写序列化类的create方法
- -把course_list弹出来
- -order=models.Order.objects.create(**validated_data)
代码:
- # urls.py
- router = SimpleRouter()
- router.register('pay', views.PayView, 'pay')
- urlpatterns = [
- path('', include(router.urls)),
- ]
-
-
- # views.py
- class PayView(GenericViewSet,CreateModelMixin):
- authentication_classes = [JSONWebTokenAuthentication,]
- permission_classes = [IsAuthenticated,]
- queryset = models.Order.objects.all()
- serializer_class = serializer.OrderSerializer
-
- # 重写create方法
- def create(self, request, *args, **kwargs):
- serializer = self.get_serializer(data=request.data,context={'request':request})
- serializer.is_valid(raise_exception=True)
- self.perform_create(serializer)
- return Response(serializer.context.get('pay_url'))
-
-
-
- # serializer.py
- class OrderSerializer(serializers.ModelSerializer):
- # 前端传什么数据过来{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,}
- # user字段需要,但是不是传的,使用了jwt
- # 需要把course:[1,2,3] 处理成 course:[obj1,obj2,obj3]
- # 课时:[1,4,6,]===>课时:[obj1,obj4,obj6,]
- # course=serializers.CharField()
-
- course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
- class Meta:
- model = models.Order
- fields = ['total_amount','subject','pay_type','course']
- extra_kwargs={
- 'total_amount':{'required':True},
- 'pay_type': {'required': True},
- }
-
-
- def _check_price(self,attrs):
- total_amount=attrs.get('total_amount')
- course_list=attrs.get('course')
- total_price=0
- for course in course_list:
- total_price+=course.price
- if total_price!=total_amount:
- raise ValidationError('价格不合法')
- return total_amount
-
- def _gen_out_trade_no(self):
- import uuid
- return str(uuid.uuid4()).replace('-','')
-
- def _get_user(self):
- # 需要request对象(需要视图通过context把reuqest对象传入。重写create方法)
- request=self.context.get('request')
- return request.user
-
- def _gen_pay_url(self,out_trade_no,total_amout,subject):
- # total_amout是Decimal类型,识别不了,需要转换成float类型
- from luffyapi.libs.al_pay import alipay,gateway
- order_string = alipay.api_alipay_trade_page_pay (
- out_trade_no=out_trade_no,
- total_amount=float(total_amout),
- subject=subject,
- return_url=settings.RETURN_URL, # get回调,前台地址
- notify_url=settings.NOTIFY_URL # post回调,后台地址
- )
- return gateway+order_string
-
- def _before_create(self,attrs,user,pay_url,out_trade_no):
- attrs['user']=user
- attrs['out_trade_no']=out_trade_no
-
- self.context['pay_url']=pay_url
- def validate(self, attrs):
- '''
- # 1)订单总价校验
- # 2)生成订单号
- # 3)支付用户:request.user
- # 4)支付链接生成
- # 5)入库(两个表)的信息准备
- '''
- # 1)订单总价校验
- total_amout = self._check_price(attrs)
- # 2)生成订单号
- out_trade_no=self._gen_out_trade_no()
- # 3)支付用户:request.user
- user=self._get_user()
- # 4)支付链接生成
- pay_url=self._gen_pay_url(out_trade_no,total_amout,attrs.get('subject'))
- # 5)入库(两个表)的信息准备
- self._before_create(attrs,user,pay_url,out_trade_no)
- return attrs
- def create(self, validated_data):
- course_list=validated_data.pop('course')
- order=models.Order.objects.create(**validated_data)
- for course in course_list:
- models.OrderDetail.objects.create(order=order,course=course,price=course.price,real_price=course.price)
-
- return order
5 前后台回调接口配置
- # dev.py
- # 上线后必须换成公网地址
- # 后台基URL
- BASE_URL = 'http://127.0.0.1:8000'
- # 前台基URL
- LUFFY_URL = 'http://127.0.0.1:8080'
- # 支付宝同步异步回调接口配置
- # 后台异步回调接口
- NOTIFY_URL = BASE_URL + "/order/success/"
- # 前台同步回调接口,没有 / 结尾
- RETURN_URL = LUFFY_URL + "/pay/success"
6 前台生成订单并跳转
- <template>
- <div class="course">
- <Header></Header>
- <div class="main">
- <!-- 筛选条件 -->
- <div class="condition">
- <ul class="cate-list">
- <li class="title">课程分类:</li>
- <li :class="filter.course_category==0?'this':''" @click="filter.course_category=0">全部</li>
- <li :class="filter.course_category==category.id?'this':''" v-for="category in category_list"
- @click="filter.course_category=category.id" :key="category.name">{{category.name}}
- </li>
- </ul>
-
- <div class="ordering">
- <ul>
- <li class="title">筛 选:</li>
- <li class="default" :class="(filter.ordering=='id' || filter.ordering=='-id')?'this':''"
- @click="filter.ordering='-id'">默认
- </li>
- <li class="hot" :class="(filter.ordering=='students' || filter.ordering=='-students')?'this':''"
- @click="filter.ordering=(filter.ordering=='-students'?'students':'-students')">人气
- </li>
- <li class="price"
- :class="filter.ordering=='price'?'price_up this':(filter.ordering=='-price'?'price_down this':'')"
- @click="filter.ordering=(filter.ordering=='-price'?'price':'-price')">价格
- </li>
- </ul>
- <p class="condition-result">共{{course_total}}个课程</p>
- </div>
-
- </div>
- <!-- 课程列表 -->
- <div class="course-list">
- <div class="course-item" v-for="course in course_list" :key="course.name">
- <div class="course-image">
- <img :src="course.course_img" alt="">
- </div>
- <div class="course-info">
- <h3>
- <router-link :to="'/free/detail/'+course.id">{{course.name}}</router-link>
- <span><img src="@/assets/img/avatar1.svg" alt="">{{course.students}}人已加入学习</span></h3>
- <p class="teather-info">
- {{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}}
- <span v-if="course.sections>course.pub_sections">共{{course.sections}}课时/已更新{{course.pub_sections}}课时</span>
- <span v-else>共{{course.sections}}课时/更新完成</span>
- </p>
- <ul class="section-list">
- <li v-for="(section, key) in course.section_list" :key="section.name"><span
- class="section-title">0{{key+1}} | {{section.name}}</span>
- <span class="free" v-if="section.free_trail">免费</span></li>
- </ul>
- <div class="pay-box">
- <div v-if="course.discount_type">
- <span class="discount-type">{{course.discount_type}}</span>
- <span class="discount-price">¥{{course.real_price}}元</span>
- <span class="original-price">原价:{{course.price}}元</span>
- </div>
- <span v-else class="discount-price">¥{{course.price}}元</span>
- <span class="buy-now" @click="buy_now(course)">立即购买</span>
- </div>
- </div>
- </div>
- </div>
- <div class="course_pagination block">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page.sync="filter.page"
- :page-sizes="[2, 3, 5, 10]"
- :page-size="filter.page_size"
- layout="sizes, prev, pager, next"
- :total="course_total">
- </el-pagination>
- </div>
- </div>
- <Footer></Footer>
- </div>
- </template>
-
- <script>
- import Header from "@/components/Head"
- import Footer from "@/components/Footer"
-
- export default {
- name: "Course",
- data() {
- return {
- category_list: [], // 课程分类列表
- course_list: [], // 课程列表
- course_total: 0, // 当前课程的总数量
- filter: {
- course_category: 0, // 当前用户选择的课程分类,刚进入页面默认为全部,值为0
- ordering: "-id", // 数据的排序方式,默认值是-id,表示对于id进行降序排列
- page_size: 2, // 单页数据量
- page: 1,
- }
- }
- },
- created() {
- this.get_category();
- this.get_course();
- },
- components: {
- Header,
- Footer,
- },
- watch: {
- //当你监听的数据发生变化,就会执行函数
- "filter.course_category": function () {
- this.filter.page = 1;
- this.get_course();
- },
- "filter.ordering": function () {
- this.get_course();
- },
- "filter.page_size": function () {
- this.get_course();
- },
- "filter.page": function () {
- this.get_course();
- }
- },
- methods: {
-
- buy_now(course) {
- let token = this.$cookies.get('token')
- if (!token) {
- this.$message({
- message: "您还没有登录,请先登录",
- })
- return false
- }
- this.$axios({
- method: 'post',
- url: this.$settings.base_url + '/order/pay/',
- data: {
- "total_amount": course.price,
- "subject": course.name,
- "pay_type": 1,
- "course": [
- course.id,
- ]
- },
- headers: {Authorization: 'jwt ' + token}
- }
- ).then(response => {
- console.log(response.data)
- let pay_url=response.data
- //前端发送get请求
- open(pay_url,'_self')
- }).catch(error => {
- })
-
- },
- handleSizeChange(val) {
- // 每页数据量发生变化时执行的方法
- this.filter.page = 1;
- this.filter.page_size = val;
- },
- handleCurrentChange(val) {
- // 页码发生变化时执行的方法
- this.filter.page = val;
- },
- get_category() {
- // 获取课程分类信息
- this.$axios.get(`${this.$settings.base_url}/course/categories/`).then(response => {
- this.category_list = response.data;
- }).catch(() => {
- this.$message({
- message: "获取课程分类信息有误,请联系客服工作人员",
- })
- })
- },
- get_course() {
- // 排序
- let filters = {
- ordering: this.filter.ordering, // 排序
- };
- // 判决是否进行分类课程的展示
- if (this.filter.course_category > 0) {
- filters.course_category = this.filter.course_category;
- }
-
- // 设置单页数据量
- if (this.filter.page_size > 0) {
- filters.page_size = this.filter.page_size;
- } else {
- filters.page_size = 5;
- }
-
- // 设置当前页码
- if (this.filter.page > 1) {
- filters.page = this.filter.page;
- } else {
- filters.page = 1;
- }
-
-
- // 获取课程列表信息
- this.$axios.get(`${this.$settings.base_url}/course/free/`, {
- params: filters
- }).then(response => {
- // console.log(response.data);
- this.course_list = response.data.results;
- this.course_total = response.data.count;
- // console.log(this.course_list);
- }).catch(() => {
- this.$message({
- message: "获取课程信息有误,请联系客服工作人员"
- })
- })
- }
- }
- }
- </script>
-
- <style scoped>
- .course {
- background: #f6f6f6;
- }
-
- .course .main {
- width: 1100px;
- margin: 35px auto 0;
- }
-
- .course .condition {
- margin-bottom: 35px;
- padding: 25px 30px 25px 20px;
- background: #fff;
- border-radius: 4px;
- box-shadow: 0 2px 4px 0 #f0f0f0;
- }
-
- .course .cate-list {
- border-bottom: 1px solid #333;
- border-bottom-color: rgba(51, 51, 51, .05);
- padding-bottom: 18px;
- margin-bottom: 17px;
- }
-
- .course .cate-list::after {
- content: "";
- display: block;
- clear: both;
- }
-
- .course .cate-list li {
- float: left;
- font-size: 16px;
- padding: 6px 15px;
- line-height: 16px;
- margin-left: 14px;
- position: relative;
- transition: all .3s ease;
- cursor: pointer;
- color: #4a4a4a;
- border: 1px solid transparent; /* transparent 透明 */
- }
-
- .course .cate-list .title {
- color: #888;
- margin-left: 0;
- letter-spacing: .36px;
- padding: 0;
- line-height: 28px;
- }
-
- .course .cate-list .this {
- color: #ffc210;
- border: 1px solid #ffc210 !important;
- border-radius: 30px;
- }
-
- .course .ordering::after {
- content: "";
- display: block;
- clear: both;
- }
-
- .course .ordering ul {
- float: left;
- }
-
- .course .ordering ul::after {
- content: "";
- display: block;
- clear: both;
- }
-
- .course .ordering .condition-result {
- float: right;
- font-size: 14px;
- color: #9b9b9b;
- line-height: 28px;
- }
-
- .course .ordering ul li {
- float: left;
- padding: 6px 15px;
- line-height: 16px;
- margin-left: 14px;
- position: relative;
- transition: all .3s ease;
- cursor: pointer;
- color: #4a4a4a;
- }
-
- .course .ordering .title {
- font-size: 16px;
- color: #888;
- letter-spacing: .36px;
- margin-left: 0;
- padding: 0;
- line-height: 28px;
- }
-
- .course .ordering .this {
- color: #ffc210;
- }
-
- .course .ordering .price {
- position: relative;
- }
-
- .course .ordering .price::before,
- .course .ordering .price::after {
- cursor: pointer;
- content: "";
- display: block;
- width: 0px;
- height: 0px;
- border: 5px solid transparent;
- position: absolute;
- right: 0;
- }
-
- .course .ordering .price::before {
- border-bottom: 5px solid #aaa;
- margin-bottom: 2px;
- top: 2px;
- }
-
- .course .ordering .price::after {
- border-top: 5px solid #aaa;
- bottom: 2px;
- }
-
- .course .ordering .price_up::before {
- border-bottom-color: #ffc210;
- }
-
- .course .ordering .price_down::after {
- border-top-color: #ffc210;
- }
-
- .course .course-item:hover {
- box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);
- }
-
- .course .course-item {
- width: 1100px;
- background: #fff;
- padding: 20px 30px 20px 20px;
- margin-bottom: 35px;
- border-radius: 2px;
- cursor: pointer;
- box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);
- /* css3.0 过渡动画 hover 事件操作 */
- transition: all .2s ease;
- }
-
- .course .course-item::after {
- content: "";
- display: block;
- clear: both;
- }
-
- /* 顶级元素 父级元素 当前元素{} */
- .course .course-item .course-image {
- float: left;
- width: 423px;
- height: 210px;
- margin-right: 30px;
- }
-
- .course .course-item .course-image img {
- max-width: 100%;
- max-height: 210px;
- }
-
- .course .course-item .course-info {
- float: left;
- width: 596px;
- }
-
- .course-item .course-info h3 a {
- font-size: 26px;
- color: #333;
- font-weight: normal;
- margin-bottom: 8px;
- }
-
- .course-item .course-info h3 span {
- font-size: 14px;
- color: #9b9b9b;
- float: right;
- margin-top: 14px;
- }
-
- .course-item .course-info h3 span img {
- width: 11px;
- height: auto;
- margin-right: 7px;
- }
-
- .course-item .course-info .teather-info {
- font-size: 14px;
- color: #9b9b9b;
- margin-bottom: 14px;
- padding-bottom: 14px;
- border-bottom: 1px solid #333;
- border-bottom-color: rgba(51, 51, 51, .05);
- }
-
- .course-item .course-info .teather-info span {
- float: right;
- }
-
- .course-item .section-list::after {
- content: "";
- display: block;
- clear: both;
- }
-
- .course-item .section-list li {
- float: left;
- width: 44%;
- font-size: 14px;
- color: #666;
- padding-left: 22px;
- /* background: url("路径") 是否平铺 x轴位置 y轴位置 */
- background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;
- margin-bottom: 15px;
- }
-
- .course-item .section-list li .section-title {
- /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- display: inline-block;
- max-width: 200px;
- }
-
- .course-item .section-list li:hover {
- background-image: url("/src/assets/img/play-icon-yellow.svg");
- color: #ffc210;
- }
-
- .course-item .section-list li .free {
- width: 34px;
- height: 20px;
- color: #fd7b4d;
- vertical-align: super;
- margin-left: 10px;
- border: 1px solid #fd7b4d;
- border-radius: 2px;
- text-align: center;
- font-size: 13px;
- white-space: nowrap;
- }
-
- .course-item .section-list li:hover .free {
- color: #ffc210;
- border-color: #ffc210;
- }
-
- .course-item {
- position: relative;
- }
-
- .course-item .pay-box {
- position: absolute;
- bottom: 20px;
- width: 600px;
- }
-
- .course-item .pay-box::after {
- content: "";
- display: block;
- clear: both;
- }
-
- .course-item .pay-box .discount-type {
- padding: 6px 10px;
- font-size: 16px;
- color: #fff;
- text-align: center;
- margin-right: 8px;
- background: #fa6240;
- border: 1px solid #fa6240;
- border-radius: 10px 0 10px 0;
- float: left;
- }
-
- .course-item .pay-box .discount-price {
- font-size: 24px;
- color: #fa6240;
- float: left;
- }
-
- .course-item .pay-box .original-price {
- text-decoration: line-through;
- font-size: 14px;
- color: #9b9b9b;
- margin-left: 10px;
- float: left;
- margin-top: 10px;
- }
-
- .course-item .pay-box .buy-now {
- width: 120px;
- height: 38px;
- background: transparent;
- color: #fa6240;
- font-size: 16px;
- border: 1px solid #fd7b4d;
- border-radius: 3px;
- transition: all .2s ease-in-out;
- float: right;
- text-align: center;
- line-height: 38px;
- position: absolute;
- right: 0;
- bottom: 5px;
- }
-
- .course-item .pay-box .buy-now:hover {
- color: #fff;
- background: #ffc210;
- border: 1px solid #ffc210;
- }
-
- .course .course_pagination {
- margin-bottom: 60px;
- text-align: center;
- }
- </style>
7 前台支付成功页面
- # 路由
- {
- path: '/pay/success',
- name: 'PaySuccess',
- 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>
- </div>
- </template>
-
- <script>
- import Header from "@/components/Head"
-
- export default {
- name: "Success",
- data() {
- return {
- result: {},
- };
- },
- created() {
- // url后拼接的参数:?及后面的所有参数 => ?a=1&b=2
- // console.log(location.search);
-
- // 解析支付宝回调的url参数
- let params = location.search.substring(1); // 去除? => a=1&b=2
- let items = params.length ? params.split('&') : []; // ['a=1', 'b=2']
- //逐个将每一项添加到args对象中
- for (let i = 0; i < items.length; i++) { // 第一次循环a=1,第二次b=2
- let k_v = items[i].split('='); // ['a', '1']
- //解码操作,因为查询字符串经过编码的
- if (k_v.length >= 2) {
- // url编码反解
- let k = decodeURIComponent(k_v[0]);
- this.result[k] = decodeURIComponent(k_v[1]);
- // 没有url编码反解
- // this.result[k_v[0]] = k_v[1];
- }
-
- }
- // 解析后的结果
- // console.log(this.result);
-
-
- // 把地址栏上面的支付结果,再get请求转发给后端
- this.$axios({
- url: this.$settings.base_url + '/order/success/' + location.search,
- method: 'get',
- }).then(response => {
- console.log(response.data);
- }).catch(() => {
- console.log('支付结果同步失败');
- })
- },
- components: {
- Header,
- }
- }
- </script>
-
- <style scoped>
- .main {
- padding: 60px 0;
- margin: 0 auto;
- width: 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;
- width: 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;
- width: 140px;
- height: 42px;
- text-align: center;
- line-height: 42px;
- cursor: pointer;
- background: #ffc210;
- border-radius: 6px;
- font-size: 16px;
- color: #fff;
- }
- </style>
8 同步异步回调接口
- class SuccessView(APIView):
- def get(self,request,*args,**kwargs):
- out_trade_no=request.query_params.get('out_trade_no')
- order=models.Order.objects.filter(out_trade_no=out_trade_no).first()
- if order.order_status==1:
- return Response(True)
- else:
- return Response(False)
-
- def post(self,request,*args,**kwargs):
- '''
- 支付宝回调接口
- '''
- from luffyapi.libs.al_pay import alipay
- from luffyapi.utils.logger import log
- data = request.data
- out_trade_no=data.get('out_trade_no',None)
- gmt_payment=data.get('gmt_payment',None)
- signature = data.pop("sign")
- # 验证签名
- success = alipay.verify(data, signature)
- if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
- models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1,pay_time=gmt_payment)
- log.info('%s订单支付成功'%out_trade_no)
- return Response('success')
- else:
- log.info('%s订单有问题' % out_trade_no)
- return Response('error')
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。