luffy之支付宝使用

luffy之支付宝使用

复习

#1 课程列表页面
#2 课程详情页面---》vue-video-player
#3 视频托管(文件)
	-直接放到项目中
    -公司自己搭建文件服务器(fastdfs,ceph,minio) 小而量大的问题
    -第三方存储:阿里oss,七牛云存储(sdk)
    -上传文件:js--》传到存储--》返回地址---》带着地址访问咱们后端接口,把地址存入数据库
    
    
    
# 支付:
	-支付宝:商户号---》营业执照---》沙箱环境
    		-文档---》没有营业执照申请商户号
    -微信:商户号---》营业执照
    -银联:sdk
    
    

	  

1 支付宝支付测试

# 支付宝支付流程
    1.前端页面点击支付按钮
    2.向后端提交下单请求
    3.后端判断是否能下单,钱数对不对等
    4.后台生成一个订单信息,存在订单表(待支付状态)
    5.后端生成一个支付链接地址给前端
    6.前端取出支付链接地址并跳转到此页面(这个地址是支付宝支付链接地址)
    7.手机扫码或者登陆账号开始支付 
    8.支付成功后 支付宝会发两个回调地址一个给后端一个给前端
    9.给后端地址是post请求(8次),通知后端已经支付成功,后端修改订单表为已支付,并回复给支付宝已收到
    10.给钱端地址是get请求,前端会去订单表查询是否支付成功
    # 以post回调作为修改订单的基准,如果post请求,返回没反应或者回复的内容不对,24小时内发8次,如果都没收到,一般不可能崩溃24小时

    
# 使用沙箱环境(支付宝支付测试使用) 官方没有pythn的sdk 
	-商户号
    -用户号

# 所以使用第三方支付宝sdk
	Python支付宝开源框架:https://github.com/fzlee/alipay
    使用 pip install python-alipay-sdk
    
# 非对称加密
	-公钥,私钥:
    公钥只能加密
    公钥可可以给任何人
    私钥只有自己有
    -加密用公钥加密,解密用私钥解密
# 对称加密:加密和解密的密码一样,



# 生成公钥私钥
	-在本地使用命令生成
    -支付网站,软件生成:https://docs.open.alipay.com/291/105971 
     
   	-支付宝支付的话,仅有公钥私钥不行,还需要支付宝公钥---》把咱们的公钥填入---》生成支付宝公钥
    

#测试代码
from alipay import AliPay

# 应用私钥
APP_PRIVATE_KEY_STRING = open('./pri').read()

# 支付宝公钥
ALIPAY_PUBLIC_KEY_STRING = open('./pub').read()

# 支付对象
pay = AliPay(
    appid='2021000119627806',
    app_notify_url=None,
    app_private_key_string=APP_PRIVATE_KEY_STRING,
    alipay_public_key_string=ALIPAY_PUBLIC_KEY_STRING,
    sign_type='RSA2',
    debug=True
)


res = pay.api_alipay_trade_page_pay(
    out_trade_no='123456',  # 订单号
    total_amount=float(99),  # 只有生成支付宝链接时,不能用Decimal
    subject='野生奥特曼',  # 订单标题
    return_url='http://127.0.0.1:8080/home',  # get
    notify_url='http://127.0.0.1:8080/home',  # post

)
payurl = 'https://openapi.alipaydev.com/gateway.do?' + res
print(payurl)	


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

2 支付宝支付二次封装 文件目录如下

2.1 pay.py

from alipay import AliPay
from . import settings

# 支付对象
pay = AliPay(
    appid=settings.APP_ID,
    app_notify_url=None,
    app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
    alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
    sign_type=settings.SIGN,
    debug=settings.DEBUG
)

# 支付网关
gateway = settings.GATEWAY

# 这个方法可写可不写 看怎么调,这里不写了 外面想使用pay.api_alipay_trade_page_pay这个方法就可以
# def go_pay():
#     res=pay.api_alipay_trade_page_pay(xx)
#     return gateway+"?"+res

2.2 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 = '2021000119627806'

# 加密方式
SIGN = 'RSA2'

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

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

3 后台支付接口

# 记得注册app

# 登录认证()jwt认证

# 前端传入参数:商品总价格,登录用户(已经登录了,不需要携带了),courses:[1,2,3] 课程id
	金额也传过来,后端会判断一下
	{total_amount:99,courses:[1,2,3]}
    
# 后端:
	-存单个Order表,顺带存Orderdetail表----》重写create
    -所有判断逻辑写到序列化类中
    	-# 订单金额校验 判断传过来的金额和根据买的课程进行对比
        -#  生成订单号   ---》uuid
        -#  获取支付人  ----》request对象中,要存到表里,要有支付人
        -#  获取支付链接--->封装好的支付宝支付
        -# 入库(两个表)的信息准备
        -# 入库----》重写create

视图类

from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from .models import Order
from utils.response import APIResponse
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from .serializer import PaySerializer

from utils.logging import logger

class OrderView(GenericViewSet, CreateModelMixin):
    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated]
    serializer_class = PaySerializer
    queryset = Order.objects.all()

    def create(self, request, *args, **kwargs):
        try:
            ser = self.get_serializer(data=request.data, context={'request': request})
            ser.is_valid(raise_exception=True)
            ser.save()
            pay_url = ser.context.get('pay_url')
        except Exception as e:
            raise e

        return APIResponse(pay_url=pay_url)

序列化类

from .models import Order, OrderDetail
from course.models import Course
from rest_framework import serializers
from rest_framework.validators import ValidationError
from libs.apay import pay,gateway
from django.conf import settings
class PaySerializer(serializers.ModelSerializer):
    # courses=serializers.ListField()  # 不用这个
    # 如果这个  courses:1--->courses:id为1的这个课程   many=False
    # 如果 courses:[1,2,3]---->courses:[对象1,对象2,对象3] q
    # queryset存放的是对象 
    courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)

    class Meta:
        model = Order
        fields = ['total_amount', 'courses']

    def validate(self, attrs):
        # attrs 中有:total_amount,courses是对象列表
        # 订单金额校验(校验传入的价格跟我们计算的价格是否一致)
        self._check_price(attrs)
        # 生成订单号   ---》uuid
        trade_no = self._get_trade_no()
        # 获取支付人  ----》request对象中
        user=self._get_user()
        # 获取支付链接--->封装好的支付宝支付
        pay_url=self.get_pay_url(attrs,trade_no)
        # 入库(两个表)的信息准备
        attrs['subject']='路飞线上课程'
        attrs['out_trade_no']=trade_no
        attrs['user']=user

        self.context['pay_url']=pay_url


        return attrs


    def _check_price(self, attrs):
        total_amount = attrs.get('total_amount')
        courses = attrs.get('courses')
        real_total_amount = 0
        for course in courses:
            real_total_amount += course.price
        if not total_amount == real_total_amount:
            raise ValidationError('价格不一致')

    def _get_trade_no(self):
        import uuid
        trade_no = str(uuid.uuid4()).replace('-', '')
        return trade_no

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


    def get_pay_url(self,attrs,trade_no):
        '''
        out_trade_no='123456',  # 订单号
        total_amount=float(99.99),  # 只有生成支付宝链接时,不能用Decimal
        subject='精品内衣',
        return_url='http://127.0.0.1:8080/home',
        notify_url='http://127.0.0.1:8080/home',

        '''
        res=pay.api_alipay_trade_page_pay(
            out_trade_no=trade_no,
            total_amount=float(attrs.get('total_amount')),
            subject='路飞线上课程',
            return_url=settings.RETURN_URL, # 放到配置文件 前端回调就是个前端页面,支付宝回调到支付成功页面
            notify_url=settings.NOTIFY_URL, # 后端回调 接收支付宝的post回调,在这里面修改订单状态
        )
        return gateway+res



    def create(self, validated_data):
        # validated_data:  [subject, out_trade_no,  user courses  total_amount]
        courses = validated_data.pop('courses')
        order=Order.objects.create(**validated_data)
        # 存订单详情表
        for course in courses:
            OrderDetail.objects.create(course=course,order=order,price=course.price,real_price=course.price)

        return order

路由

router.register('pay', views.OrderView, 'pay')

4 前端支付功能

 go_pay(total_amount, course_id) {
                // cookie中有token值,就是登录了
                let token = this.$cookies.get('token')
                if (token) {
                    this.$axios({
                        url: this.$settings.base_url + 'order/pay/',
                        method: 'post',
                        headers: {
                            Authorization: 'jwt ' + token,
                        },
                        data: {
                            'total_amount': total_amount,
                            'courses': [course_id,]
                        }
                    }).then(res => {
                        if (res.data.code == 100) {
                            // 跳转到支付页面
                            location.href = res.data.pay_url
                        }
                    })
                    // this.$axios.post(
                    //     this.$settings.base_url + 'order/pay/',
                    //     {
                    //         'total_amount': total_amount,
                    //         'courses': [course_id,]
                    //     }, {
                    //         headers: {
                    //             'Authorization': 'jwt ' + token
                    //         },
                    //     }
                    // ).then(res => {
                    //     if (res.data.code == 100) {
                    //         // 跳转到支付页面
                    //         location.href = res.data.pay_url
                    //     }
                    // })
                } else {
                    this.$message({
                        message: "您没有登录,请先登录",
                    })
                }

            }

5 支付成功回调接口

# 正常只需要一个post回调给支付宝用(登录认证?支付宝要验证签名---》才信任,才改订单状态)
# 咱们写了一个get回调,给前端用,做双重验证保险一些


# 内网穿透:https://zhuanlan.zhihu.com/p/370483324




# 前端代码编译
	npm run build
    
    
 
from rest_framework.views import APIView
from .models import Order
class PaySuccessView(APIView):
    def get(self,request): # 自己用的
        try:
            out_trade_no=request.query_params.get('out_trade_no')
            # 根据订单号,查订单的状态
            Order.objects.get(out_trade_no=out_trade_no,order_status=1)
        except Exception as e:
            raise e
        return APIResponse()

    # 给支付宝用---》回调回来数据格式
    def post(self,request):
        # django :requset.POST  request.GET --->QueryDict的对象--》不允许删除数据
        try:
            result_data = request.data.dict()  # request.data 是post的数据,dict()作用是复制了一份数据
            out_trade_no = result_data.get('out_trade_no') # 订单号
            signature = result_data.pop('sign') # 签名
            from libs import apay
            # 验证签名,只有这个正确,才能修改订单状态--》防止恶意发送post请求给我们
            result = apay.pay.verify(result_data, signature)
            if result and result_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
                # 完成订单修改:订单状态、流水号、支付时间
                models.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')

6 git上传前端需要转成静态页面

执行:npm run build
会在目录下生成一个dist文件夹,里面的就是静态页面
posted @ 2022-03-06 12:25  迪迦张  阅读(75)  评论(0编辑  收藏  举报