• Django+Vue打造购物网站(九)


    支付宝沙箱环境配置

    https://openhome.alipay.com/platform/appDaily.htm?tab=info

    使用支付宝账号进行登陆

    RSA私钥及公钥生成

    https://docs.open.alipay.com/291/105971/
    下载工具,按文档提示使用

    在trade文件夹下新建keys文件夹,新建public.txtprivate.txt两个文件
    trade/keys/private.txt

    -----BEGIN RSA PRIVATE KEY-----
    MIIEpAIBAAKCAQEAvRYNqz/3Ye+ewQeRfvLXuAu5N/+bdC3KBj8L63j
    -----END RSA PRIVATE KEY-----
    

    trade/keys/public.txt

    -----BEGIN RSA PRIVATE KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvRYN
    -----END RSA PRIVATE KEY-----
    

    中间的字符串是工具中生成的公钥和私钥(应用公钥)
    将应用公钥上传到沙箱应用中

    在新建一个alipay_key.txt文件
    trade/keys/alipay_key.txt

    -----BEGIN RSA PRIVATE KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCA
    -----END RSA PRIVATE KEY-----
    

    中间的是支付宝公钥

    支付文档说明
    https://docs.open.alipay.com/270

    代码实现

    首先的安装一个模块

    pip install pycryptodome
    

    utils中新建alipay.py

    from datetime import datetime
    from Crypto.PublicKey import RSA
    
    from Crypto.Signature import PKCS1_v1_5
    from Crypto.Hash import SHA256
    from base64 import b64encode, b64decode
    from urllib.parse import quote_plus
    from urllib.parse import urlparse, parse_qs
    from urllib.request import urlopen
    from base64 import decodebytes, encodebytes
    
    import json
    
    
    class AliPay(object):
        """
        支付宝支付接口
        """
    
        def __init__(self, appid, app_notify_url, app_private_key_path,
                     alipay_public_key_path, return_url, debug=False):
            self.appid = appid
            self.app_notify_url = app_notify_url
            # 私钥
            self.app_private_key_path = app_private_key_path
            self.app_private_key = None
            self.return_url = return_url
            with open(self.app_private_key_path) as fp:
                self.app_private_key = RSA.importKey(fp.read())
            # 公钥
            self.alipay_public_key_path = alipay_public_key_path
            with open(self.alipay_public_key_path) as fp:
                self.alipay_public_key = RSA.import_key(fp.read())
    
            if debug is True:
                self.__gateway = "https://openapi.alipaydev.com/gateway.do"
            else:
                self.__gateway = "https://openapi.alipay.com/gateway.do"
    
        def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
            # 请求参数
            biz_content = {
                "subject": subject,
                "out_trade_no": out_trade_no,
                "total_amount": total_amount,
                "product_code": "FAST_INSTANT_TRADE_PAY",
                # "qr_pay_mode":4
            }
            # 允许传递更多参数,放到biz_content
            biz_content.update(kwargs)
            data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
            return self.sign_data(data)
    
        def build_body(self, method, biz_content, return_url=None):
            # build_body主要生产消息的格式
            # 公共请求参数
            data = {
                "app_id": self.appid,
                "method": method,
                "charset": "utf-8",
                "sign_type": "RSA2",
                "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                "version": "1.0",
                "biz_content": biz_content
            }
    
            if return_url is not None:
                data["notify_url"] = self.app_notify_url
                data["return_url"] = self.return_url
    
            return data
    
        def sign_data(self, data):
            # 签名
            data.pop("sign", None)
            # 排序后的字符串
            unsigned_items = self.ordered_data(data)
            # 排完序后拼接起来
            unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
            # 这里得到签名的字符串
            sign = self.sign(unsigned_string.encode("utf-8"))
            # 对url进行处理
            quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)
    
            # 获得最终的订单信息字符串
            signed_string = quoted_string + "&sign=" + quote_plus(sign)
            return signed_string
    
        # 参数传进来一定要排序
        def ordered_data(self, data):
            complex_keys = []
            for key, value in data.items():
                if isinstance(value, dict):
                    complex_keys.append(key)
    
            # 将字典类型的数据dump出来
            for key in complex_keys:
                data[key] = json.dumps(data[key], separators=(',', ':'))
    
            return sorted([(k, v) for k, v in data.items()])
    
        def sign(self, unsigned_string):
            # 开始计算签名
            key = self.app_private_key
            # 签名的对象
            signer = PKCS1_v1_5.new(key)
            # 生成签名
            signature = signer.sign(SHA256.new(unsigned_string))
            # base64 编码,转换为unicode表示并移除回车
            sign = encodebytes(signature).decode("utf8").replace("
    ", "")
            return sign
    
        def _verify(self, raw_content, signature):
            # 开始计算签名
            key = self.alipay_public_key
            signer = PKCS1_v1_5.new(key)
            digest = SHA256.new()
            digest.update(raw_content.encode("utf8"))
            if signer.verify(digest, decodebytes(signature.encode("utf8"))):
                return True
            return False
    
        def verify(self, data, signature):
            if "sign_type" in data:
                sign_type = data.pop("sign_type")
            # 排序后的字符串
            unsigned_items = self.ordered_data(data)
            message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
            return self._verify(message, signature)
    
    
    if __name__ == "__main__":
        return_url = 'http://127.0.0.1:8000/?total_amount=100.00&timestamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2FvhdDYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdpVQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&method=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0'
        o = urlparse(return_url)
        query = parse_qs(o.query)
        processed_query = {}
        ali_sign = query.pop("sign")[0]
    
        # 测试用例
        alipay = AliPay(
            # 沙箱里面的appid值
            appid="2016091500517596",
            # notify_url是异步的url
            app_notify_url="http://127.0.0.1:8000/",
            # 我们自己商户的密钥
            app_private_key_path="../trade/keys/private.txt",
            # 支付宝的公钥
            alipay_public_key_path="../trade/keys/alipay_key.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            # debug为true时使用沙箱的url。如果不是用正式环境的url
            debug=True,  # 默认False,
            return_url="http://127.0.0.1:8000/alipay/return/"
        )
    
        for key, value in query.items():
            processed_query[key] = value[0]
        # print (alipay.verify(processed_query, ali_sign))
    
        # 直接支付:生成请求的字符串。
        url = alipay.direct_pay(
            # 订单标题
            subject="测试订单",
            # 我们商户自行生成的订单号
            out_trade_no="201809270840",
            # 订单金额
            total_amount=100,
            # 成功付款后跳转到的页面,return_url同步的url
            # return_url="http://127.0.0.1:8000/"
        )
        # 将生成的请求字符串拿到我们的url中进行拼接
        re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    
        print(re_url)
    

    运行这个文件,点击生成的链接,就跳转到了支付界面

    django集成支付宝

    urls.py

    # 配置支付宝支付相关接口的url
    path('alipay/return/', AlipayView.as_view())
    

    alipay.py

    # 把return_url和notify_url都改成远程服务器的地址
    
    return_url="http://xxx.xxx.xxx.xxx:8000/alipay/return/"
    
    app_notify_url="http://xxx.xxx.xxx.xxx:8000/alipay/return/"
    

    settings.py中配置公钥私钥路径

    # 支付宝相关的key
    private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/private.txt')
    ali_pub_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/alipay_key.txt')
    

    trade/views.py

    from datetime import datetime
    from utils.alipay import AliPay
    from rest_framework.views import APIView
    from MxShop.settings import ali_pub_key_path, private_key_path
    from rest_framework.response import Response
    
    
    class AlipayView(APIView):
        def get(self, request):
            """
            处理支付宝的return_url返回
            """
            processed_dict = {}
            # 1. 获取GET中参数
            for key, value in request.GET.items():
                processed_dict[key] = value
            # 2. 取出sign
            sign = processed_dict.pop("sign", None)
    
            # 3. 生成ALipay对象
            alipay = AliPay(
                appid="201609xxxx456",
                app_notify_url="http://xxx.xxx.xxx.xxx:8000/alipay/return/",
                app_private_key_path=private_key_path,
                alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
                debug=True,  # 默认False,
                return_url="http://xxx.xxx.xxx.xxx:8000/alipay/return/"
            )
    
            verify_re = alipay.verify(processed_dict, sign)
    
            # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。
            if verify_re is True:
                order_sn = processed_dict.get('out_trade_no', None)
                trade_no = processed_dict.get('trade_no', None)
                trade_status = processed_dict.get('trade_status', None)
    
                existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
                for existed_order in existed_orders:
                    existed_order.pay_status = trade_status
                    existed_order.trade_no = trade_no
                    existed_order.pay_time = datetime.now()
                    existed_order.save()
    
        def post(self, request):
            """
            处理支付宝的notify_url
            """
            # 存放post里面所有的数据
            processed_dict = {}
            # 取出post里面的数据
            for key, value in request.POST.items():
                processed_dict[key] = value
            # 把signpop掉,文档有说明
            sign = processed_dict.pop("sign", None)
    
            # 生成一个Alipay对象
            alipay = AliPay(
                appid="201609xxxx456",
                app_notify_url="http://xxx.xxx.xxx.xxx:8000/alipay/return/",
                app_private_key_path=private_key_path,
                alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
                debug=True,  # 默认False,
                return_url="http://xxx.xxx.xxx.xxx:8000/alipay/return/"
            )
    
            # 进行验证
            verify_re = alipay.verify(processed_dict, sign)
    
            # 如果验签成功
            if verify_re is True:
                # 商户网站唯一订单号
                order_sn = processed_dict.get('out_trade_no', None)
                # 支付宝系统交易流水号
                trade_no = processed_dict.get('trade_no', None)
                # 交易状态
                trade_status = processed_dict.get('trade_status', None)
    
                # 查询数据库中订单记录
                existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
                for existed_order in existed_orders:
                    # 订单商品项
                    order_goods = existed_order.goods.all()
                    # 商品销量增加订单中数值
                    for order_good in order_goods:
                        goods = order_good.goods
                        goods.sold_num += order_good.goods_num
                        goods.save()
    
                    # 更新订单状态
                    existed_order.pay_status = trade_status
                    existed_order.trade_no = trade_no
                    existed_order.pay_time = datetime.now()
                    existed_order.save()
                # 需要返回一个'success'给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息
                return Response("success")
    

    trade/serializers.py

    创建订单的时候生成一个支付的url,这个逻辑OderSerializerOrderDetailSerializer中都添加

     #支付订单的url
        alipay_url = serializers.SerializerMethodField(read_only=True)
    
        def get_alipay_url(self, obj):
            alipay = AliPay(
                appid="20160xxxx17456",
                app_notify_url="http://4xx.xxx.xxx.xx59:8000/alipay/return/",
                app_private_key_path=private_key_path,
                alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
                debug=True,  # 默认False,
                return_url="http://4xx.xxx.xxx.xx59:8000/alipay/return/"
            )
    
            url = alipay.direct_pay(
                subject=obj.order_sn,
                out_trade_no=obj.order_sn,
                total_amount=obj.order_mount,
            )
            re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    
            return re_url
    

    服务器测试

    把vue项目中api.js里面local_host改为服务器ip

    let local_host = 'http://47.93.198.159:8000';
    

    在pycharm中调用服务器的解释器运行代码

    浏览器访问地址:http://47.93.198.159:8000/orders/

    输入数据进行测试

    进入返回的alipay的url进行测试

    测试的时候发现,明明支付成功了,数据库中的状态却没了,
    既不是paying,也不是TRADE_SUCCESS,就是一个空字符串
    这是因为支付宝的同步跳转参数中没有支付状态,而我们却在get请求中去获取这个参数
    这里需要将get函数中的获取交易状态删除(注释掉)
    https://docs.open.alipay.com/270/alipay.trade.page.pay

    # trade_status = processed_dict.get('trade_status', None)
    

    vue静态文件放到django中

    运行npm run build会生成一个dist目录,打开这个目录

    把index.html拷贝到templates目录下
    把index.entry.js考到django的static目录下面
    把dist/static下的两个文件夹拷贝到static目录下
    修改js路径

    配置index的url

    复制代码
    from django.views.generic import TemplateView
    
        # 首页
        path('index/', TemplateView.as_view(template_name='index.html'),name='index')
    
    

    配置支付成功return的地址

            if verify_re is True:
                order_sn = processed_dict.get('out_trade_no', None)
                trade_no = processed_dict.get('trade_no', None)
                # trade_status = processed_dict.get('trade_status', None)
    
                existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
                for existed_order in existed_orders:
                    # existed_order.pay_status = trade_status
                    existed_order.trade_no = trade_no
                    existed_order.pay_time = datetime.now()
                    existed_order.save()
    
                response = redirect("/index/#/app/home/member/order")
                return response
            else:
                response = redirect("index")
                return response
    

    同步到服务器,浏览器访问进行测试
    http://IP:8000/index/#/app/home/index

    class AlipayView(APIView)

    from datetime import datetime
    from utils.alipay import AliPay
    from rest_framework.views import APIView
    from MxShop.settings import ali_pub_key_path, private_key_path
    from rest_framework.response import Response
    
    
    class AlipayView(APIView):
        def get(self, request):
            """
            处理支付宝的return_url返回
            """
            processed_dict = {}
            # 1. 获取GET中参数
            for key, value in request.GET.items():
                processed_dict[key] = value
            # 2. 取出sign
            sign = processed_dict.pop("sign", None)
    
            # 3. 生成ALipay对象
            alipay = AliPay(
                appid="2016091500517596",
                app_notify_url="http://120.79.65.88:8000/alipay/return/",
                app_private_key_path=private_key_path,
                alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
                debug=True,  # 默认False,
                return_url="http://120.79.65.88:8000/alipay/return/"
            )
    
            verify_re = alipay.verify(processed_dict, sign)
    
            # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。
            if verify_re is True:
                order_sn = processed_dict.get('out_trade_no', None)
                trade_no = processed_dict.get('trade_no', None)
                # trade_status = processed_dict.get('trade_status', None)
    
                existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
                for existed_order in existed_orders:
                    # existed_order.pay_status = trade_status
                    existed_order.trade_no = trade_no
                    existed_order.pay_time = datetime.now()
                    existed_order.save()
    
                response = redirect("/index/#/app/home/member/order")
                return response
            else:
                response = redirect("index")
                return response
    
        def post(self, request):
            """
            处理支付宝的notify_url
            """
            # 存放post里面所有的数据
            processed_dict = {}
            # 取出post里面的数据
            for key, value in request.POST.items():
                processed_dict[key] = value
            # 把signpop掉,文档有说明
            sign = processed_dict.pop("sign", None)
    
            # 生成一个Alipay对象
            alipay = AliPay(
                appid="2016091500517596",
                app_notify_url="http://120.79.65.88:8000/alipay/return/",
                app_private_key_path=private_key_path,
                alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
                debug=True,  # 默认False,
                return_url="http://120.79.65.88:8000/alipay/return/"
            )
    
            # 进行验证
            verify_re = alipay.verify(processed_dict, sign)
    
            # 如果验签成功
            if verify_re is True:
                # 商户网站唯一订单号
                order_sn = processed_dict.get('out_trade_no', None)
                # 支付宝系统交易流水号
                trade_no = processed_dict.get('trade_no', None)
                # 交易状态
                trade_status = processed_dict.get('trade_status', None)
    
                # 查询数据库中订单记录
                existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
                for existed_order in existed_orders:
                    # 订单商品项
                    order_goods = existed_order.goods.all()
                    # 商品销量增加订单中数值
                    for order_good in order_goods:
                        goods = order_good.goods
                        goods.sold_num += order_good.goods_num
                        goods.save()
    
                    # 更新订单状态
                    existed_order.pay_status = trade_status
                    existed_order.trade_no = trade_no
                    existed_order.pay_time = datetime.now()
                    existed_order.save()
                # 需要返回一个'success'给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息
                return Response("success")
    
  • 相关阅读:
    使用熔断器防止服务雪崩
    创建服务消费者(Feign)
    1.python进行if条件相等时候的条件
    理解编程语言是什么
    硬件架构与操作系统的历史
    centos7 下安装rpm的mysql 5.7
    BIND的进阶二:视图,日志,转发,子域的授权
    Linux启动盘-ultraiso
    ubuntu 跟xshell的问题
    Python接口自动化-requests模块之get请求
  • 原文地址:https://www.cnblogs.com/gaoyongjian/p/9985907.html
Copyright © 2020-2023  润新知