Python实战项目-10文件存储/支付宝支付/支付成功回调接口

每天进步一点点,关注我哦,每天分享测试技术文章

文件存储

视频文件存储在某个位置,如果放在自己服务器上

  • 放在项目的media文件夹
  • 服务器上线后,用户既要访问接口,又需要看视频,都是使用一个域名和端口
  • 分开:问价你单独放在文件服务器上,文件服务器带宽比较高

# 文件服务器:专门存储文件的服务器 -第三方: -阿里云:对象存储 oss -腾讯对象存储 -七牛云存储 -自己搭建: fastdfs:文件对象存储 https://zhuanlan.zhihu.com/p/372286804 minio:

免费领取码同学软件测试课程笔记+超多学习资料+完整视频+面试题,可加交流群:【1134725192 】

我们可以使用对应的sdk包将文件传输上去

在此项目中我们选用七牛云来存储视频文件资源

使用代码,上传视频

我们参考官方文档使用即可

1.创建七牛云对象存储仓库

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第1张图片

2.直接在桌面上传文件即可

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第2张图片

1.1代码控制文件上传

python安装七牛云
pip install qiniu
本地测试
我们scripts文件夹下新建qiniu_test.py文件

# -*- coding: utf-8 -*-
# flake8: noqa
from qiniu import Auth, put_file, etag
import qiniu.config
#需要填写你的 Access Key 和 Secret Key
# 在这里查看密钥 > https://portal.qiniu.com/user/key
access_key = 'Access_Key'
secret_key = 'Secret_Key'
#构建鉴权对象
q = Auth(access_key, secret_key)
#要上传的空间
bucket_name = 'Bucket_Name'
#上传后保存的文件名
key = 'my-python-logo.png'
#生成上传 Token,可以指定过期时间等
token = q.upload_token(bucket_name, key, 3600)
#要上传文件的本地路径
localfile = './sync/bbb.jpg'
ret, info = put_file(token, key, localfile, version='v2') 
print(info)
assert ret['key'] == key
assert ret['hash'] == etag(localfile)

尝试上传本地文件:

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第3张图片

成功

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第4张图片

搜索导航栏

前端Header组件上有个搜索框>>>输入内容,即可搜索
在所有商城类的网站,app都会有搜索功能,其实搜索功能非常复杂,且功能非常复杂技术含量高

  • 咱们目前只是简单的搜索,输入课程名字/价格,就可以把实战课搜出来
  • 输入:课程名字,价格把所有类型课程都搜出来(查询多个表)
  • 后面会有专门的搜索引擎:分布式全文检索引擎 es 做专门的搜索

前端页面Header.vue





搜索页面

 






搜索接口

class CourseSearchView(GenericViewSet, CommonListModelMixin):
    queryset = Course.objects.all().filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = CourseSerializer
    pagination_class = CommonPageNumberPagination
    filter_backends = [SearchFilter]
    search_fields = ['name']

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第5张图片

支付宝支付介绍

前端点击立即购买功能,会生成订单并跳转到付款界面

# 支付宝支付
	-测试环境:大家都可以测试
    	-https://openhome.alipay.com/develop/sandbox/app
    -正式环境:需要申请,有营业执照

咱们开发虽然用的沙箱环境,后期上线,公司会自己注册,
注册成功后有个商户id号,作为开发,只要有商户id号,其他步骤都是一样,
所有无论开发还是测试,代码都一样,只是商户号不一样

使用支付宝支付

  • API接口

  • SDK:优先使用,早期支付宝没有python的sdk,后期有了

-使用了第三方sdk -第三方人通过api接口,使用python封装了sdk,开源出来了

沙箱环境
-安卓的支付宝app,付款用的(买家用)
-扫码使用这个app,付款,这个app的钱都是假的,付款测试商户(卖家)

支付测试,生成支付链接

安装
pip install python-alipay-sdk
生成公钥私钥

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第6张图片

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第7张图片

我们可以将生成的公钥配置在支付宝的(沙箱环境)上,生成一个支付宝公钥
以后我们使用这个支付宝公钥即可

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第8张图片

我们需要将支付宝的公钥,以及项目的应用私钥放入项目中
-pub.pem
-pri.pem
注意:
我们的公钥密钥需要符合要求格式

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第9张图片

教程参考:alipay/tests/certs/ali at master · fzlee/alipay · GitHub

支付测试代码:

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第10张图片

from alipay import AliPay
from alipay.utils import AliPayConfig
app_private_key_string = open("pri.pem").read()
alipay_public_key_string = open("pub.pem").read()
alipay = AliPay(
    appid="2021000122628354", # 沙盒支付宝appid
    app_notify_url=None,  # 默认回调 url
    app_private_key_string=app_private_key_string,
    # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    alipay_public_key_string=alipay_public_key_string,
    sign_type="RSA2",  # RSA 或者 RSA2
    debug=False,  # 默认 False
    verbose=False,  # 输出调试数据
    config=AliPayConfig(timeout=15)  # 可选,请求超时时间
)
res=alipay.api_alipay_trade_page_pay(subject='基尼台妹', out_trade_no='asdbasbdjqweo', total_amount='2888')
print('https://openapi.alipaydev.com/gateway.do?'+res)

运行脚本获取链接,打开

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第11张图片

 支付宝支付二次封装

目录结构

libs
    ├── iPay  							# aliapy二次封装包
    │   ├── __init__.py 				# 包文件
    │   ├── pem							# 公钥私钥文件夹
    │   │   ├── alipay_public_key.pem	# 支付宝公钥文件
    │   │   ├── app_private_key.pem		# 应用私钥文件
    │   ├── pay.py						# 支付文件
    └── └── settings.py  				# 应用配置  

init.py 

from .pay import alipay
from .settings import GETWAY

pay.py

from alipay import AliPay
from alipay.utils import AliPayConfig
from . import settings
alipay = AliPay(
    appid=settings.APP_ID,
    app_notify_url=None,  # 默认回调 url
    app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
    # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
    sign_type=settings.SIGN,  # RSA 或者 RSA2
    debug=settings.DEBUG,  # 默认 False
    verbose=settings.DEBUG,  # 输出调试数据
    config=AliPayConfig(timeout=15)  # 可选,请求超时时间
)

 settings.py

import os

# 应用私钥
APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'app_private_key.pem')).read()

# 支付宝公钥
ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'alipay_public_key.pem')).read()

# 应用ID
APP_ID = '22222222222'

# 加密方式
SIGN = 'RSA2'

# 是否是支付宝测试环境(沙箱环境),如果采用真是支付宝环境,配置False
DEBUG = True

# 支付网关
GATEWAY = 'https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'

订单表设计

-订单表
-订单详情表

下单接口-->没有支付是订单时待支付状态
支付宝post回调接口--> 修改订单状态 --已完成
前端get回调接口

我们需要新建order app

models.py

# Create your models here.
# 订单板块需要写的接口
# 新建order 的app,在models.py中写入表
from django.db import models

from django.db import models
from course.models import Course

'''
ForeignKey 中on_delete 
    -CASCADE  级联删除
    -DO_NOTHING    啥都不做,没有外键约束才能用它
    -SET_NULL       字段置为空,字段 null=True
    -SET_DEFAULT   设置为默认值,default='xx'
    -PROTECT    受保护的,很少用
    -models.SET(函数内存地址)   会设置成set内的值

'''
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)
    # 订单号,咱们后端生成的,唯一:后期支付宝回调回来的数据会带着这个订单号,根据这个订单号修改订单状态
    # 使用什么生成? uuid(可能重复,概率很多)    【分布式id的生成】  雪花算法
    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="支付时间")
    # 跟用户一对多    models.DO_NOTHING
    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='创建时间')

    class Meta:
        db_table = "luffy_order"
        verbose_name = "订单记录"
        verbose_name_plural = "订单记录"

    def __str__(self):
        return "%s - ¥%s" % (self.subject, self.total_amount)


class OrderDetail(models.Model):
    """订单详情"""
    # related_name 反向查询替换表名小写_set
    # on_delete 级联删除
    # db_constraint=False ----》默认是True,会在表中为Order何OrderDetail创建外键约束
    # db_constraint=False  没有外键约束,插入数据 速度快,  可能会产生脏数据【不合理】,所以咱们要用程序控制,以后公司惯用的
    # 对到数据库上,它是不建立外键,基于对象的跨表查,基于连表的查询,继续用,跟之前没有任何区别
    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.DO_NOTHING, 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):
        try:
            return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
        except:
            return super().__str__()

 执行迁移命令>>>

下单接口

接口分析:
	用户登录后才能使用
	前端点击立即购买 ---> post请求携带数据 
    {courses:[1,],total_amount:99.9,subject:'xx课程'}
	视图类中重写create方法
	将主要逻辑写到序列化类中
# 主要逻辑:
	1 取出所有课程id号,拿到课程
    2 统计总价格,跟传入的total_amount做比较,如果一样,继续往后
    3 获取购买人信息:登录后才能访问的接口 request.user
    4 生成订单号 支付链接需要,存订单表需要
    5 生成支付链接:支付宝支付生成,
    6 生成订单记录,订单是待支付状态(order,order_detail)
    7 返回前端支付链接

 路由

from rest_framework.routers import SimpleRouter
from . import views

router = SimpleRouter()
router.register('pay', views.PayView, 'pay')
urlpatterns = [
    # path('',include(router.urls))
]
urlpatterns += router.urls

 视图层

from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from .models import Order
from .serializer import PaySerializer
from utils.response import APIResponse
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
# Create your views here.
class PayView(GenericViewSet,CreateModelMixin):
    queryset = Order.objects.all()
    serializer_class = PaySerializer
    authentication_classes = [JSONWebTokenAuthentication] # 使用JWT权限类配置必须配权限类
    permission_classes = [IsAuthenticated]

    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)
        pay_url = serializer.context.get("pay_url")
        return APIResponse(pay_url=pay_url)

序列化类

# 校验字段,反序列化      不会序列化的
class PaySerializer(serializers.ModelSerializer):
    # courses 不是表的字段,需要重写--->新东西
    # courses=serializers.ListField()  # 咱们不用这种  courses=[1,2,3]

    # 前端传入的 courses=[1,2,3]--->根据queryset对应的qs对象 做映射,映射成courses=[课程对象1,课程对象2,课程对象3]
    courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), many=True)

    class Meta:
        model = Order
        fields = ['courses', 'total_amount', 'subject']  # 前端传入的字段是什么,这里就写什么

    def _check_total_amount(self, attrs):
        courses = attrs.get('courses')  # 课程对象列表  [课程对象1,课程对象2]
        total_amount = attrs.get('total_amount')
        new_total_amount = 0
        for course in courses:
            new_total_amount += course.price
        if total_amount == new_total_amount:
            return new_total_amount
        raise APIException('价格有误!!')

    def _get_out_trade_no(self):
        # uuid生成
        return str(uuid.uuid4())

    def _get_user(self):
        user = self.context.get('request').user
        return user

    def _get_pay_url(self, out_trade_no, total_amount, subject):
        # 生成支付链接
        res = alipay.api_alipay_trade_page_pay(
            total_amount=float(total_amount),
            subject=subject,
            out_trade_no=out_trade_no,
            return_url=settings.RETURN_URL,  # 前端的
            notify_url=settings.NOTIFY_URL  # 后端接口,写这个接口该订单状态

        )
        # return GATEWAY + res
        self.context['pay_url'] = GATEWAY + res

    def _before_create(self, attrs, user, out_trade_no):
        # 剔除courses----》要不要剔除,要pop,但是不在这,在create方法中pop
        # 订单号,加入到attrs中
        attrs['out_trade_no'] = out_trade_no
        # 把user加入到attrs中
        attrs['user'] = user

    def validate(self, attrs):
        # 1)订单总价校验
        total_amount = self._check_total_amount(attrs)
        # 2)生成订单号
        out_trade_no = self._get_out_trade_no()
        # 3)支付用户:request.user
        user = self._get_user()
        # 4)支付链接生成
        self._get_pay_url(out_trade_no, total_amount, attrs.get('subject'))

        # 5)入库(两个表)的信息准备
        self._before_create(attrs, user, out_trade_no)
        return attrs

    # 生成订单,存订单表,一定要重写create,存俩表
    def create(self, validated_data):
        # validated_data:{subject,total_amount,user,out_trade_no,courses}
        courses = validated_data.pop('courses')
        order = Order.objects.create(**validated_data)
        # 存订单详情表,存几条,取决于courses有几个
        for course in courses:
            OrderDetail.objects.create(order=order, course=course, price=course.price, real_price=course.price)

        return order
序列化类中要使用request对象,所以可以将request传入context上下文,在序列化类使用。

我们还是在全局钩子里写逻辑。
分析我们要使用序列化类做的事情:校验字段、反序列化。(不做序列化)
courses不是订单表的字段,需要在序列化类重写。courses是个列表,需要使用ListField。但是还有别的方法:

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第12张图片

因为是反序列化多条数据,所以要加many=True

注意我们必须要登录之后才能获取订单链接,

我们使用权限类+认证类来限制登录用户下单

前端支付页面

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第13张图片

需要携带token向后端发送请求。
数据库查看订单状态
 

支付成功后会回调到前端地址

所以要在前端再写一个支付成功页面:

CourseDetail.vue

go_pay() {
      // 判断是否登录
      let token = this.$cookies.get('token')
      if (token) {
        this.$axios.post(this.$settings.BASE_URL + '/order/pay/', {
          subject: this.course_info.name,
          total_amount: this.course_info.price,
          courses: [this.course_id]
        }, {
          headers: {
            Authorization: `jwt ${token}`
          }
        }).then(res => {
          if (res.data.code == 100) {
            // 打开支付连接地址
            open(res.data.pay_url, '_self');
          } else {
            this.$message(res.data.msg)
          }
        })
      } else {
        this.$message('您没有登录,请先登录')
      }
    }

PaySuccess.vue





支付成功回调接口

# 支付成功,支付宝会有俩回调
	-get 回调,调前端
    	-为了保证准确性,支付宝回调会前端后,我们自己向后端发送一个请求,查询一下这个订单是否支付成功
    -post 回调,调后端接口
    	-后端接口,接受支付宝的回调,修改订单状态
        -这个接口需要登录吗?不需要任何的认证和权限
        -如果用户点了支付----》跳转到了支付宝页面---》你的服务挂机了---》会出现什么情况
        	-支付宝在24小时内,会有8次回调,
            
            
# 两个接口:
	-post回调,给支付宝用
    -get回调,给我们前端做二次校验使用

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第14张图片

由于我们现在处于内网,所以接收不到回调信息

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第15张图片

class PaySuccess(APIView):
    def get(self, request):  # 咱们用的
        out_trade_no = request.query_params.get('out_trade_no')
        order = Order.objects.filter(out_trade_no=out_trade_no, order_status=1).first()
        if order:  # 支付宝回调完, 订单状态改了
            return APIResponse()
        else:
            return APIResponse(code=101, msg='暂未收到您的付款,请稍后刷新再试')

    def post(self, request):  # 给支付宝用的,项目需要上线后才能看到  内网中,无法回调成功【使用内网穿透】
        try:
            result_data = request.data.dict()  # requset.data 是post提交的数据,如果是urlencoded格式,requset.data是QueryDict对象,方法dict()---》转成真正的字典
            out_trade_no = result_data.get('out_trade_no')
            signature = result_data.pop('sign')
            # 验证签名的---》验签
            result = alipay_v1.alipay.verify(result_data, signature)
            if result and result_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
                # 完成订单修改:订单状态、流水号、支付时间
                Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1)
                # 完成日志记录
                logger.warning('%s订单支付成功' % out_trade_no)
                return Response('success')  # 都是支付宝要求的
            else:
                logger.error('%s订单支付失败' % out_trade_no)
        except:
            pass
        return Response('failed')  # 都是支付宝要求的

Response的格式需要符合支付宝要求。如果支付宝回调回不去了(后端崩了),48小时之内支付宝会进行8次回调,任意一次回调成功就可以了(给支付宝返回success)。如果8次回调都没有收到,还有一个对账单的功能。
这两个接口是否需要添加认证?
不能加任何认证和权限,会导致支付宝无法回调。加个频率没关系。

最后:

这些资料,对于想转行做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。希望对大家有所帮助……

Python实战项目-10文件存储/支付宝支付/支付成功回调接口_第16张图片

在这里插入图片描述

你可能感兴趣的:(开发语言,职场和发展,自动化,程序人生,测试工具)