当前位置:   article > 正文

超时未支付算不算取消一次订单_luffy-10.3 支付宝

超时未支付算不算取消一次订单_luffy-10.3 支付宝

# 沙箱环境:登录 - 支付宝

# 支付宝开放平台文档 下载软件,生成秘钥(公钥私钥)

# 取出公钥,配置再支付宝平台,它会生成一个支付宝公钥

37dd4f32a8bbc3b6992213dc1be452f4.png

d19e7e94083e5ef79d79b4e082187e1a.png

代码:新建文件夹

  1. # pip install python-alipay-sdk
  2. from alipay import AliPay
  3. # app_private_key_string = open("/path/to/your/private/key.pem").read()
  4. # alipay_public_key_string = open("/path/to/alipay/public/key.pem").read()
  5. app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
  6. 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==
  7. -----END RSA PRIVATE KEY-----
  8. """
  9. alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
  10. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwU9mMZHHlQPE9FcxVtXOXhbWCtuDZLJRVCiofdbTVmRXrx47yGniPehwcKIsqhzhEaXBG2QhpIZUL8YsCav0mkrppoRvWOytuGyxNRESo8I6DWRs0aCq6P3AuiD9kSXET4dpAuRYT/+JrMXIZTycEts6vYYNAT9QivXJoa2FmiCQBAL3HP7F36pby9VstObilxXQcoBBJwEYGf2TK6moFFZ1dkloRr5Cfk/G82DpuVfrt1gr4OuIDWtcE3MZTrvDgTqtkRuwGF76FY3+8xUCUbJs1dL5cXYN7/b3jPcXVcdKXFj4WrOQd42ofE1BJWMxBW7L3Qlxue1vy+NGx/CuKwIDAQAB
  11. -----END PUBLIC KEY-----
  12. """
  13. alipay = AliPay(
  14. appid="2016092000554611",
  15. app_notify_url='http://127.0.0.1:8000/home/', # the default notify path
  16. app_private_key_string=app_private_key_string,
  17. # alipay public key, do not use your own public key!
  18. alipay_public_key_string=alipay_public_key_string,
  19. sign_type="RSA2", # RSA or RSA2
  20. debug=True # False by default
  21. )
  22. alipay_url='https://openapi.alipaydev.com/gateway.do?'
  23. order_string = alipay.api_alipay_trade_page_pay (
  24. out_trade_no="20161112www4334",
  25. total_amount=9999,
  26. subject='韩红版充气娃娃',
  27. return_url="https://www.luffycity.com/free-course",
  28. notify_url="https://www.luffycity.com/free-course"
  29. )
  30. print(alipay_url+order_string)

二 支付宝的二次封装:

  1. al_pay
  2. -pem
  3. -__init__.py
  4. -pay.py
  5. -setting.py
  6. #__init__.py
  7. from .pay import alipay,gateway
  8. #pay.py
  9. from alipay import AliPay
  10. from . import setting
  11. alipay = AliPay(
  12. appid=setting.APPID,
  13. app_notify_url=None, # the default notify path
  14. app_private_key_string=setting.APP_PRIVATE_KEY_STRING,
  15. # alipay public key, do not use your own public key!
  16. alipay_public_key_string=setting.ALIPAY_PUBLIC_KEY_STRING,
  17. sign_type=setting.SIGN_TYPE, # RSA or RSA2
  18. debug=setting.DEBUG # False by default
  19. )
  20. gateway=setting.GATEWAY
  21. # setting.py
  22. import os
  23. APPID="2016092000554611"
  24. APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','private_key.pem')).read()
  25. ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','al_public_key.pem')).read()
  26. SIGN_TYPE='RSA2'
  27. DEBUG=True
  28. GATEWAY='https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'

三 订单模块与表分析

  1. # 订单表分析
  2. -订单表
  3. -订单详情
  4. from django.db import models
  5. from django.db import models
  6. from user.models import User
  7. from course.models import Course
  8. # 订单表
  9. class Order(models.Model):
  10. """订单模型"""
  11. status_choices = (
  12. (0, '未支付'),
  13. (1, '已支付'),
  14. (2, '已取消'),
  15. (3, '超时取消'),
  16. )
  17. pay_choices = (
  18. (1, '支付宝'),
  19. (2, '微信支付'),
  20. )
  21. subject = models.CharField(max_length=150, verbose_name="订单标题")
  22. total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0)
  23. out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True)
  24. trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号") # 支付宝生成回来的
  25. order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
  26. pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
  27. pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
  28. # 一个用户可以下多个订单,一个订单只属于一个用户,一对多的关系,关联字段写在多个一方,写在order
  29. user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户")
  30. created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
  31. updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')
  32. class Meta:
  33. db_table = "luffy_order"
  34. verbose_name = "订单记录"
  35. verbose_name_plural = "订单记录"
  36. def __str__(self):
  37. return "%s - ¥%s" % (self.subject, self.total_amount)
  38. @property
  39. def courses(self):
  40. data_list = []
  41. for item in self.order_courses.all():
  42. data_list.append({
  43. "id": item.id,
  44. "course_name": item.course.name,
  45. "real_price": item.real_price,
  46. })
  47. return data_list
  48. # 订单详情表
  49. # 订单和详情是一对多,关联字段写在多个的一方,写在订单详情表中
  50. class OrderDetail(models.Model):
  51. """订单详情"""
  52. order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False, verbose_name="订单")
  53. course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.SET_NULL, db_constraint=False, verbose_name="课程",null=True)
  54. price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
  55. real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")
  56. class Meta:
  57. db_table = "luffy_order_detail"
  58. verbose_name = "订单详情"
  59. verbose_name_plural = "订单详情"
  60. def __str__(self):
  61. try:
  62. return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
  63. except:
  64. return super().__str__()

4 支付接口

分析:

  1. # 1 前端传什么格式数据
  2. -{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,}
  3. # 2 后端接到数据要校验
  4. -course:[1,2,3]===》course:[obj1,obj2,obj3]
  5. -在序列化类中: course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
  6. # 3 在序列化类的validate中处理一堆逻辑
  7. '''
  8. # 1)订单总价校验:
  9. -一个个课程价格取出来累加看是否等于传入的总价格
  10. # 2)生成订单号
  11. -通过uuid生成
  12. # 3)支付用户:request.user
  13. -通过视图和序列化类之间的桥梁context对象传递
  14. -重新视图类中的create方法,把request对象放入context
  15. -self.context.get('request').user
  16. # 4)支付链接生成
  17. -导入封装的支付宝支付,生成
  18. order_string = alipay.api_alipay_trade_page_pay (
  19. out_trade_no=out_trade_no,
  20. total_amount=total_amout,
  21. subject=subject,
  22. return_url=settings.RETURN_URL, # get回调,前台地址
  23. notify_url=settings.NOTIFY_URL # post回调,后台地址
  24. )
  25. # 5)入库(两个表)的信息准备
  26. -把user放入attrs中
  27. -把订单号,放入attrs中
  28. attrs['user']=user
  29. attrs['out_trade_no']=out_trade_no
  30. self.context['pay_url']=pay_url
  31. '''
  32. # 4 重写序列化类的create方法
  33. -把course_list弹出来
  34. -order=models.Order.objects.create(**validated_data)

代码:

  1. # urls.py
  2. router = SimpleRouter()
  3. router.register('pay', views.PayView, 'pay')
  4. urlpatterns = [
  5. path('', include(router.urls)),
  6. ]
  7. # views.py
  8. class PayView(GenericViewSet,CreateModelMixin):
  9. authentication_classes = [JSONWebTokenAuthentication,]
  10. permission_classes = [IsAuthenticated,]
  11. queryset = models.Order.objects.all()
  12. serializer_class = serializer.OrderSerializer
  13. # 重写create方法
  14. def create(self, request, *args, **kwargs):
  15. serializer = self.get_serializer(data=request.data,context={'request':request})
  16. serializer.is_valid(raise_exception=True)
  17. self.perform_create(serializer)
  18. return Response(serializer.context.get('pay_url'))
  19. # serializer.py
  20. class OrderSerializer(serializers.ModelSerializer):
  21. # 前端传什么数据过来{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,}
  22. # user字段需要,但是不是传的,使用了jwt
  23. # 需要把course:[1,2,3] 处理成 course:[obj1,obj2,obj3]
  24. # 课时:[1,4,6,]===>课时:[obj1,obj4,obj6,]
  25. # course=serializers.CharField()
  26. course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
  27. class Meta:
  28. model = models.Order
  29. fields = ['total_amount','subject','pay_type','course']
  30. extra_kwargs={
  31. 'total_amount':{'required':True},
  32. 'pay_type': {'required': True},
  33. }
  34. def _check_price(self,attrs):
  35. total_amount=attrs.get('total_amount')
  36. course_list=attrs.get('course')
  37. total_price=0
  38. for course in course_list:
  39. total_price+=course.price
  40. if total_price!=total_amount:
  41. raise ValidationError('价格不合法')
  42. return total_amount
  43. def _gen_out_trade_no(self):
  44. import uuid
  45. return str(uuid.uuid4()).replace('-','')
  46. def _get_user(self):
  47. # 需要request对象(需要视图通过context把reuqest对象传入。重写create方法)
  48. request=self.context.get('request')
  49. return request.user
  50. def _gen_pay_url(self,out_trade_no,total_amout,subject):
  51. # total_amout是Decimal类型,识别不了,需要转换成float类型
  52. from luffyapi.libs.al_pay import alipay,gateway
  53. order_string = alipay.api_alipay_trade_page_pay (
  54. out_trade_no=out_trade_no,
  55. total_amount=float(total_amout),
  56. subject=subject,
  57. return_url=settings.RETURN_URL, # get回调,前台地址
  58. notify_url=settings.NOTIFY_URL # post回调,后台地址
  59. )
  60. return gateway+order_string
  61. def _before_create(self,attrs,user,pay_url,out_trade_no):
  62. attrs['user']=user
  63. attrs['out_trade_no']=out_trade_no
  64. self.context['pay_url']=pay_url
  65. def validate(self, attrs):
  66. '''
  67. # 1)订单总价校验
  68. # 2)生成订单号
  69. # 3)支付用户:request.user
  70. # 4)支付链接生成
  71. # 5)入库(两个表)的信息准备
  72. '''
  73. # 1)订单总价校验
  74. total_amout = self._check_price(attrs)
  75. # 2)生成订单号
  76. out_trade_no=self._gen_out_trade_no()
  77. # 3)支付用户:request.user
  78. user=self._get_user()
  79. # 4)支付链接生成
  80. pay_url=self._gen_pay_url(out_trade_no,total_amout,attrs.get('subject'))
  81. # 5)入库(两个表)的信息准备
  82. self._before_create(attrs,user,pay_url,out_trade_no)
  83. return attrs
  84. def create(self, validated_data):
  85. course_list=validated_data.pop('course')
  86. order=models.Order.objects.create(**validated_data)
  87. for course in course_list:
  88. models.OrderDetail.objects.create(order=order,course=course,price=course.price,real_price=course.price)
  89. return order

5 前后台回调接口配置

  1. # dev.py
  2. # 上线后必须换成公网地址
  3. # 后台基URL
  4. BASE_URL = 'http://127.0.0.1:8000'
  5. # 前台基URL
  6. LUFFY_URL = 'http://127.0.0.1:8080'
  7. # 支付宝同步异步回调接口配置
  8. # 后台异步回调接口
  9. NOTIFY_URL = BASE_URL + "/order/success/"
  10. # 前台同步回调接口,没有 / 结尾
  11. RETURN_URL = LUFFY_URL + "/pay/success"

6 前台生成订单并跳转

  1. <template>
  2. <div class="course">
  3. <Header></Header>
  4. <div class="main">
  5. <!-- 筛选条件 -->
  6. <div class="condition">
  7. <ul class="cate-list">
  8. <li class="title">课程分类:</li>
  9. <li :class="filter.course_category==0?'this':''" @click="filter.course_category=0">全部</li>
  10. <li :class="filter.course_category==category.id?'this':''" v-for="category in category_list"
  11. @click="filter.course_category=category.id" :key="category.name">{{category.name}}
  12. </li>
  13. </ul>
  14. <div class="ordering">
  15. <ul>
  16. <li class="title">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</li>
  17. <li class="default" :class="(filter.ordering=='id' || filter.ordering=='-id')?'this':''"
  18. @click="filter.ordering='-id'">默认
  19. </li>
  20. <li class="hot" :class="(filter.ordering=='students' || filter.ordering=='-students')?'this':''"
  21. @click="filter.ordering=(filter.ordering=='-students'?'students':'-students')">人气
  22. </li>
  23. <li class="price"
  24. :class="filter.ordering=='price'?'price_up this':(filter.ordering=='-price'?'price_down this':'')"
  25. @click="filter.ordering=(filter.ordering=='-price'?'price':'-price')">价格
  26. </li>
  27. </ul>
  28. <p class="condition-result">共{{course_total}}个课程</p>
  29. </div>
  30. </div>
  31. <!-- 课程列表 -->
  32. <div class="course-list">
  33. <div class="course-item" v-for="course in course_list" :key="course.name">
  34. <div class="course-image">
  35. <img :src="course.course_img" alt="">
  36. </div>
  37. <div class="course-info">
  38. <h3>
  39. <router-link :to="'/free/detail/'+course.id">{{course.name}}</router-link>
  40. <span><img src="@/assets/img/avatar1.svg" alt="">{{course.students}}人已加入学习</span></h3>
  41. <p class="teather-info">
  42. {{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}}
  43. <span v-if="course.sections>course.pub_sections">共{{course.sections}}课时/已更新{{course.pub_sections}}课时</span>
  44. <span v-else>共{{course.sections}}课时/更新完成</span>
  45. </p>
  46. <ul class="section-list">
  47. <li v-for="(section, key) in course.section_list" :key="section.name"><span
  48. class="section-title">0{{key+1}} | {{section.name}}</span>
  49. <span class="free" v-if="section.free_trail">免费</span></li>
  50. </ul>
  51. <div class="pay-box">
  52. <div v-if="course.discount_type">
  53. <span class="discount-type">{{course.discount_type}}</span>
  54. <span class="discount-price">¥{{course.real_price}}元</span>
  55. <span class="original-price">原价:{{course.price}}元</span>
  56. </div>
  57. <span v-else class="discount-price">¥{{course.price}}元</span>
  58. <span class="buy-now" @click="buy_now(course)">立即购买</span>
  59. </div>
  60. </div>
  61. </div>
  62. </div>
  63. <div class="course_pagination block">
  64. <el-pagination
  65. @size-change="handleSizeChange"
  66. @current-change="handleCurrentChange"
  67. :current-page.sync="filter.page"
  68. :page-sizes="[2, 3, 5, 10]"
  69. :page-size="filter.page_size"
  70. layout="sizes, prev, pager, next"
  71. :total="course_total">
  72. </el-pagination>
  73. </div>
  74. </div>
  75. <Footer></Footer>
  76. </div>
  77. </template>
  78. <script>
  79. import Header from "@/components/Head"
  80. import Footer from "@/components/Footer"
  81. export default {
  82. name: "Course",
  83. data() {
  84. return {
  85. category_list: [], // 课程分类列表
  86. course_list: [], // 课程列表
  87. course_total: 0, // 当前课程的总数量
  88. filter: {
  89. course_category: 0, // 当前用户选择的课程分类,刚进入页面默认为全部,值为0
  90. ordering: "-id", // 数据的排序方式,默认值是-id,表示对于id进行降序排列
  91. page_size: 2, // 单页数据量
  92. page: 1,
  93. }
  94. }
  95. },
  96. created() {
  97. this.get_category();
  98. this.get_course();
  99. },
  100. components: {
  101. Header,
  102. Footer,
  103. },
  104. watch: {
  105. //当你监听的数据发生变化,就会执行函数
  106. "filter.course_category": function () {
  107. this.filter.page = 1;
  108. this.get_course();
  109. },
  110. "filter.ordering": function () {
  111. this.get_course();
  112. },
  113. "filter.page_size": function () {
  114. this.get_course();
  115. },
  116. "filter.page": function () {
  117. this.get_course();
  118. }
  119. },
  120. methods: {
  121. buy_now(course) {
  122. let token = this.$cookies.get('token')
  123. if (!token) {
  124. this.$message({
  125. message: "您还没有登录,请先登录",
  126. })
  127. return false
  128. }
  129. this.$axios({
  130. method: 'post',
  131. url: this.$settings.base_url + '/order/pay/',
  132. data: {
  133. "total_amount": course.price,
  134. "subject": course.name,
  135. "pay_type": 1,
  136. "course": [
  137. course.id,
  138. ]
  139. },
  140. headers: {Authorization: 'jwt ' + token}
  141. }
  142. ).then(response => {
  143. console.log(response.data)
  144. let pay_url=response.data
  145. //前端发送get请求
  146. open(pay_url,'_self')
  147. }).catch(error => {
  148. })
  149. },
  150. handleSizeChange(val) {
  151. // 每页数据量发生变化时执行的方法
  152. this.filter.page = 1;
  153. this.filter.page_size = val;
  154. },
  155. handleCurrentChange(val) {
  156. // 页码发生变化时执行的方法
  157. this.filter.page = val;
  158. },
  159. get_category() {
  160. // 获取课程分类信息
  161. this.$axios.get(`${this.$settings.base_url}/course/categories/`).then(response => {
  162. this.category_list = response.data;
  163. }).catch(() => {
  164. this.$message({
  165. message: "获取课程分类信息有误,请联系客服工作人员",
  166. })
  167. })
  168. },
  169. get_course() {
  170. // 排序
  171. let filters = {
  172. ordering: this.filter.ordering, // 排序
  173. };
  174. // 判决是否进行分类课程的展示
  175. if (this.filter.course_category > 0) {
  176. filters.course_category = this.filter.course_category;
  177. }
  178. // 设置单页数据量
  179. if (this.filter.page_size > 0) {
  180. filters.page_size = this.filter.page_size;
  181. } else {
  182. filters.page_size = 5;
  183. }
  184. // 设置当前页码
  185. if (this.filter.page > 1) {
  186. filters.page = this.filter.page;
  187. } else {
  188. filters.page = 1;
  189. }
  190. // 获取课程列表信息
  191. this.$axios.get(`${this.$settings.base_url}/course/free/`, {
  192. params: filters
  193. }).then(response => {
  194. // console.log(response.data);
  195. this.course_list = response.data.results;
  196. this.course_total = response.data.count;
  197. // console.log(this.course_list);
  198. }).catch(() => {
  199. this.$message({
  200. message: "获取课程信息有误,请联系客服工作人员"
  201. })
  202. })
  203. }
  204. }
  205. }
  206. </script>
  207. <style scoped>
  208. .course {
  209. background: #f6f6f6;
  210. }
  211. .course .main {
  212. width: 1100px;
  213. margin: 35px auto 0;
  214. }
  215. .course .condition {
  216. margin-bottom: 35px;
  217. padding: 25px 30px 25px 20px;
  218. background: #fff;
  219. border-radius: 4px;
  220. box-shadow: 0 2px 4px 0 #f0f0f0;
  221. }
  222. .course .cate-list {
  223. border-bottom: 1px solid #333;
  224. border-bottom-color: rgba(51, 51, 51, .05);
  225. padding-bottom: 18px;
  226. margin-bottom: 17px;
  227. }
  228. .course .cate-list::after {
  229. content: "";
  230. display: block;
  231. clear: both;
  232. }
  233. .course .cate-list li {
  234. float: left;
  235. font-size: 16px;
  236. padding: 6px 15px;
  237. line-height: 16px;
  238. margin-left: 14px;
  239. position: relative;
  240. transition: all .3s ease;
  241. cursor: pointer;
  242. color: #4a4a4a;
  243. border: 1px solid transparent; /* transparent 透明 */
  244. }
  245. .course .cate-list .title {
  246. color: #888;
  247. margin-left: 0;
  248. letter-spacing: .36px;
  249. padding: 0;
  250. line-height: 28px;
  251. }
  252. .course .cate-list .this {
  253. color: #ffc210;
  254. border: 1px solid #ffc210 !important;
  255. border-radius: 30px;
  256. }
  257. .course .ordering::after {
  258. content: "";
  259. display: block;
  260. clear: both;
  261. }
  262. .course .ordering ul {
  263. float: left;
  264. }
  265. .course .ordering ul::after {
  266. content: "";
  267. display: block;
  268. clear: both;
  269. }
  270. .course .ordering .condition-result {
  271. float: right;
  272. font-size: 14px;
  273. color: #9b9b9b;
  274. line-height: 28px;
  275. }
  276. .course .ordering ul li {
  277. float: left;
  278. padding: 6px 15px;
  279. line-height: 16px;
  280. margin-left: 14px;
  281. position: relative;
  282. transition: all .3s ease;
  283. cursor: pointer;
  284. color: #4a4a4a;
  285. }
  286. .course .ordering .title {
  287. font-size: 16px;
  288. color: #888;
  289. letter-spacing: .36px;
  290. margin-left: 0;
  291. padding: 0;
  292. line-height: 28px;
  293. }
  294. .course .ordering .this {
  295. color: #ffc210;
  296. }
  297. .course .ordering .price {
  298. position: relative;
  299. }
  300. .course .ordering .price::before,
  301. .course .ordering .price::after {
  302. cursor: pointer;
  303. content: "";
  304. display: block;
  305. width: 0px;
  306. height: 0px;
  307. border: 5px solid transparent;
  308. position: absolute;
  309. right: 0;
  310. }
  311. .course .ordering .price::before {
  312. border-bottom: 5px solid #aaa;
  313. margin-bottom: 2px;
  314. top: 2px;
  315. }
  316. .course .ordering .price::after {
  317. border-top: 5px solid #aaa;
  318. bottom: 2px;
  319. }
  320. .course .ordering .price_up::before {
  321. border-bottom-color: #ffc210;
  322. }
  323. .course .ordering .price_down::after {
  324. border-top-color: #ffc210;
  325. }
  326. .course .course-item:hover {
  327. box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);
  328. }
  329. .course .course-item {
  330. width: 1100px;
  331. background: #fff;
  332. padding: 20px 30px 20px 20px;
  333. margin-bottom: 35px;
  334. border-radius: 2px;
  335. cursor: pointer;
  336. box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);
  337. /* css3.0 过渡动画 hover 事件操作 */
  338. transition: all .2s ease;
  339. }
  340. .course .course-item::after {
  341. content: "";
  342. display: block;
  343. clear: both;
  344. }
  345. /* 顶级元素 父级元素 当前元素{} */
  346. .course .course-item .course-image {
  347. float: left;
  348. width: 423px;
  349. height: 210px;
  350. margin-right: 30px;
  351. }
  352. .course .course-item .course-image img {
  353. max-width: 100%;
  354. max-height: 210px;
  355. }
  356. .course .course-item .course-info {
  357. float: left;
  358. width: 596px;
  359. }
  360. .course-item .course-info h3 a {
  361. font-size: 26px;
  362. color: #333;
  363. font-weight: normal;
  364. margin-bottom: 8px;
  365. }
  366. .course-item .course-info h3 span {
  367. font-size: 14px;
  368. color: #9b9b9b;
  369. float: right;
  370. margin-top: 14px;
  371. }
  372. .course-item .course-info h3 span img {
  373. width: 11px;
  374. height: auto;
  375. margin-right: 7px;
  376. }
  377. .course-item .course-info .teather-info {
  378. font-size: 14px;
  379. color: #9b9b9b;
  380. margin-bottom: 14px;
  381. padding-bottom: 14px;
  382. border-bottom: 1px solid #333;
  383. border-bottom-color: rgba(51, 51, 51, .05);
  384. }
  385. .course-item .course-info .teather-info span {
  386. float: right;
  387. }
  388. .course-item .section-list::after {
  389. content: "";
  390. display: block;
  391. clear: both;
  392. }
  393. .course-item .section-list li {
  394. float: left;
  395. width: 44%;
  396. font-size: 14px;
  397. color: #666;
  398. padding-left: 22px;
  399. /* background: url("路径") 是否平铺 x轴位置 y轴位置 */
  400. background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;
  401. margin-bottom: 15px;
  402. }
  403. .course-item .section-list li .section-title {
  404. /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */
  405. text-overflow: ellipsis;
  406. overflow: hidden;
  407. white-space: nowrap;
  408. display: inline-block;
  409. max-width: 200px;
  410. }
  411. .course-item .section-list li:hover {
  412. background-image: url("/src/assets/img/play-icon-yellow.svg");
  413. color: #ffc210;
  414. }
  415. .course-item .section-list li .free {
  416. width: 34px;
  417. height: 20px;
  418. color: #fd7b4d;
  419. vertical-align: super;
  420. margin-left: 10px;
  421. border: 1px solid #fd7b4d;
  422. border-radius: 2px;
  423. text-align: center;
  424. font-size: 13px;
  425. white-space: nowrap;
  426. }
  427. .course-item .section-list li:hover .free {
  428. color: #ffc210;
  429. border-color: #ffc210;
  430. }
  431. .course-item {
  432. position: relative;
  433. }
  434. .course-item .pay-box {
  435. position: absolute;
  436. bottom: 20px;
  437. width: 600px;
  438. }
  439. .course-item .pay-box::after {
  440. content: "";
  441. display: block;
  442. clear: both;
  443. }
  444. .course-item .pay-box .discount-type {
  445. padding: 6px 10px;
  446. font-size: 16px;
  447. color: #fff;
  448. text-align: center;
  449. margin-right: 8px;
  450. background: #fa6240;
  451. border: 1px solid #fa6240;
  452. border-radius: 10px 0 10px 0;
  453. float: left;
  454. }
  455. .course-item .pay-box .discount-price {
  456. font-size: 24px;
  457. color: #fa6240;
  458. float: left;
  459. }
  460. .course-item .pay-box .original-price {
  461. text-decoration: line-through;
  462. font-size: 14px;
  463. color: #9b9b9b;
  464. margin-left: 10px;
  465. float: left;
  466. margin-top: 10px;
  467. }
  468. .course-item .pay-box .buy-now {
  469. width: 120px;
  470. height: 38px;
  471. background: transparent;
  472. color: #fa6240;
  473. font-size: 16px;
  474. border: 1px solid #fd7b4d;
  475. border-radius: 3px;
  476. transition: all .2s ease-in-out;
  477. float: right;
  478. text-align: center;
  479. line-height: 38px;
  480. position: absolute;
  481. right: 0;
  482. bottom: 5px;
  483. }
  484. .course-item .pay-box .buy-now:hover {
  485. color: #fff;
  486. background: #ffc210;
  487. border: 1px solid #ffc210;
  488. }
  489. .course .course_pagination {
  490. margin-bottom: 60px;
  491. text-align: center;
  492. }
  493. </style>

7 前台支付成功页面

  1. # 路由
  2. {
  3. path: '/pay/success',
  4. name: 'PaySuccess',
  5. component: PaySuccess
  6. },
  7. #PaySuccess.vue
  8. <template>
  9. <div class="pay-success">
  10. <!--如果是单独的页面,就没必要展示导航栏(带有登录的用户)-->
  11. <Header/>
  12. <div class="main">
  13. <div class="title">
  14. <div class="success-tips">
  15. <p class="tips">您已成功购买 1 门课程!</p>
  16. </div>
  17. </div>
  18. <div class="order-info">
  19. <p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p>
  20. <p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p>
  21. <p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p>
  22. </div>
  23. <div class="study">
  24. <span>立即学习</span>
  25. </div>
  26. </div>
  27. </div>
  28. </template>
  29. <script>
  30. import Header from "@/components/Head"
  31. export default {
  32. name: "Success",
  33. data() {
  34. return {
  35. result: {},
  36. };
  37. },
  38. created() {
  39. // url后拼接的参数:?及后面的所有参数 => ?a=1&b=2
  40. // console.log(location.search);
  41. // 解析支付宝回调的url参数
  42. let params = location.search.substring(1); // 去除? => a=1&b=2
  43. let items = params.length ? params.split('&') : []; // ['a=1', 'b=2']
  44. //逐个将每一项添加到args对象中
  45. for (let i = 0; i < items.length; i++) { // 第一次循环a=1,第二次b=2
  46. let k_v = items[i].split('='); // ['a', '1']
  47. //解码操作,因为查询字符串经过编码的
  48. if (k_v.length >= 2) {
  49. // url编码反解
  50. let k = decodeURIComponent(k_v[0]);
  51. this.result[k] = decodeURIComponent(k_v[1]);
  52. // 没有url编码反解
  53. // this.result[k_v[0]] = k_v[1];
  54. }
  55. }
  56. // 解析后的结果
  57. // console.log(this.result);
  58. // 把地址栏上面的支付结果,再get请求转发给后端
  59. this.$axios({
  60. url: this.$settings.base_url + '/order/success/' + location.search,
  61. method: 'get',
  62. }).then(response => {
  63. console.log(response.data);
  64. }).catch(() => {
  65. console.log('支付结果同步失败');
  66. })
  67. },
  68. components: {
  69. Header,
  70. }
  71. }
  72. </script>
  73. <style scoped>
  74. .main {
  75. padding: 60px 0;
  76. margin: 0 auto;
  77. width: 1200px;
  78. background: #fff;
  79. }
  80. .main .title {
  81. display: flex;
  82. -ms-flex-align: center;
  83. align-items: center;
  84. padding: 25px 40px;
  85. border-bottom: 1px solid #f2f2f2;
  86. }
  87. .main .title .success-tips {
  88. box-sizing: border-box;
  89. }
  90. .title img {
  91. vertical-align: middle;
  92. width: 60px;
  93. height: 60px;
  94. margin-right: 40px;
  95. }
  96. .title .success-tips {
  97. box-sizing: border-box;
  98. }
  99. .title .tips {
  100. font-size: 26px;
  101. color: #000;
  102. }
  103. .info span {
  104. color: #ec6730;
  105. }
  106. .order-info {
  107. padding: 25px 48px;
  108. padding-bottom: 15px;
  109. border-bottom: 1px solid #f2f2f2;
  110. }
  111. .order-info p {
  112. display: -ms-flexbox;
  113. display: flex;
  114. margin-bottom: 10px;
  115. font-size: 16px;
  116. }
  117. .order-info p b {
  118. font-weight: 400;
  119. color: #9d9d9d;
  120. white-space: nowrap;
  121. }
  122. .study {
  123. padding: 25px 40px;
  124. }
  125. .study span {
  126. display: block;
  127. width: 140px;
  128. height: 42px;
  129. text-align: center;
  130. line-height: 42px;
  131. cursor: pointer;
  132. background: #ffc210;
  133. border-radius: 6px;
  134. font-size: 16px;
  135. color: #fff;
  136. }
  137. </style>

8 同步异步回调接口

  1. class SuccessView(APIView):
  2. def get(self,request,*args,**kwargs):
  3. out_trade_no=request.query_params.get('out_trade_no')
  4. order=models.Order.objects.filter(out_trade_no=out_trade_no).first()
  5. if order.order_status==1:
  6. return Response(True)
  7. else:
  8. return Response(False)
  9. def post(self,request,*args,**kwargs):
  10. '''
  11. 支付宝回调接口
  12. '''
  13. from luffyapi.libs.al_pay import alipay
  14. from luffyapi.utils.logger import log
  15. data = request.data
  16. out_trade_no=data.get('out_trade_no',None)
  17. gmt_payment=data.get('gmt_payment',None)
  18. signature = data.pop("sign")
  19. # 验证签名
  20. success = alipay.verify(data, signature)
  21. if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
  22. models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1,pay_time=gmt_payment)
  23. log.info('%s订单支付成功'%out_trade_no)
  24. return Response('success')
  25. else:
  26. log.info('%s订单有问题' % out_trade_no)
  27. return Response('error')
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/379883
推荐阅读
相关标签
  

闽ICP备14008679号