支付宝签名与验签,return_url和通知页notify_url

1 支付宝签名与验签

  • 签名与验签的作用
    首先了解下使用RSA签名与验签的作用是什么?
    对数据进行签名后,可以保证数据完整性,机密性和发送方角色的不可抵赖性,可以有效防止请求信息信息被篡改。

以商户服务端(A)、支付宝服务端(B)、发送的消息内容(C)、加密后的签名(D)举例如下:
• 签名:商户A将需要发给支付宝B的消息内容C利用app私钥进行加密得到签名D。然后A将C和D一起发给B。
• 验签:支付宝B接收到内容C和签名D后,使用app公钥进行验证,验证签名D解密后的内容是否与内容C相同,相同返回true验签成功。反之,返回false验签失败。

以上是商户端的加密流程,支付宝端签名和验签流程是刚好与之相反,支付宝端签名,商户端验签。所以在整个接口请求过程中一共用到了两对密钥,商户端的应用密钥和支付宝端的支付宝密钥。深入了解可参考上期提到的《密钥交互原理》。

1.1 商户服务器签名

商户服务器生成支付页面API url请求参数,这些参数需要使用app应用私钥加密,而支付宝收到数据后,使用app应用公钥解密后与原始请求参数对比,以确定确实是商户服务器发送的请求。

1.2 商户服务器端自行实现签名过程

过程: 筛选请求参数并排序==>拼接==>使用app应用私钥加密==>拼接成url
(然后使用该url向支付宝发起请求,支付宝返回支付页面)

准备的参数如下:

REQUEST URL: https://openapi.alipay.com/gateway.do
REQUEST METHOD: GET
CONTENT:
app_id=2016101800718925
method=alipay.trade.page.pay
charset=utf-8
sign_type=RSA2
timestamp=2020-10-13 09:58:50
version=1.0
biz_content={"out_trade_no":"20150519815610100992007","product_code":"FAST_INSTANT_TRADE_PAY","total_amount":88.88,"subject":"Iphone6 16G","body":"Iphone6 16G"}
return_url="https://example.com"
notify_url="https://example.com/notify"

  • 1、筛选
    获取所有需要发送给支付宝的请求参数(详见API的参数需求说明),不包括字节类型参数,如文件、字节流,剔除 sign 字段,剔除值为空的参数。

请求参数筛选之后形式如下:

app_id=2016101800718925
method=alipay.trade.page.pay
charset=utf-8
sign_type=RSA2
timestamp=2020-10-13 09:58:50
version=1.0
biz_content={"out_trade_no":"20150519815610100992007","product_code":"FAST_INSTANT_TRADE_PAY","total_amount":88.88,"subject":"Iphone6 16G","body":"Iphone6 16G"}
return_url="https://example.com"
notify_url="https://example.com/notify"
  • 2、排序
    按照第一个字符的键值 ASCII 码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值 ASCII 码递增排序,以此类推

排序之后形式如下:

app_id=2016101800718925
biz_content={"out_trade_no":"20150519815610100992007","product_code":"FAST_INSTANT_TRADE_PAY","total_amount":88.88,"subject":"Iphone6 16G","body":"Iphone6 16G"}
charset=utf-8
format=json
method=alipay.trade.page.pay
notify_url="https://example.com/notify"
return_url="https://example.com"
sign_type=RSA2
timestamp=2020-10-13 09:58:50
version=1.0
  • 3、拼接
    将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用 & 字符连接起来,此时生成的字符串为待签名字符串

生成的待签名内容形式如下:

app_id=2016101800718925&biz_content={"out_trade_no":"20150519815610100992007","product_code":"FAST_INSTANT_TRADE_PAY","total_amount":88.88,"subject":"Iphone6 16G","body":"Iphone6 16G"}&charset=utf-8&format=json&method=alipay.trade.page.pay&notify_url="https://example.com/notify"&
return_url="https://example.com"sign_type=RSA2&timestamp=2020-10-13 09:58:50&version=1.0
  • 4、加密
    使用各自语言对应的 SHA256WithRSA(对应sign_type为RSA2)或SHA1WithRSA(对应sign_type为RSA)签名函数利用商户私钥对待签名字符串进行签名,并进行 Base64 编码,得出的加密字符串集存储于sign这个参数中

使用app应用私钥加密(必须与应用 ID 中上传的应用公钥是匹配的,支付宝会使用应用公钥解密:即验签),
app应用私钥形式如下:

MIIEpQIBAAKCAQEAv/JKebY7hSXhg1q3V2Z1KCTtfCDLBIhfaLM3RqkHUsxoqdiXYzzJKX7f/hIVdkL/6Gza0PTDbG3dohCx5G2yIo6Gariq4IyFk4cDc3jIooi4kShz4nVzEgS333ZdmZ3LHRcGf2353INUp55Sv0dOMDkLeZA09lJujisJ5UGfgeFM79v+a1tvlcwPnMQmbF9t9ddBhH6/WDZgEL+x2EaytX9+tyhRLffA12OewgI8e0DACk2XSO1vyOtYl5e3LI/DVjByiwzUr+bWpqNKAm4PmzTL5OFvZBE5LwQSJ/qYu6pA2BKuxmn65lsBk/sGBwMJa/WezZIb+BALsyyOKWxVCwIDAQABAoIBAG9FnjcAlXCSjTEAndhk5PXosmOK/yYZiHXBrwGfa0dsiCAuF1TIIDWV/3PiN97e6EttD0yjF8b7ycfxta6eiO3PgczMUQLrc2QamL2P/395ksVTlhppy9NeONmqXIh5GQ48EuA8eOSEncat2XpZc9Iwv54xIwLItp5kBNCKQlWfrNKJUp1ZjpVBfh1fENjktkdyN9uzZp6PInuEH5WM//4tTGi78oGzDtg1r8DwHS5KqXYo9ulEOyd0KH1OAYXZLqjYJ2xIzotlvu7WlWxYFwK5+D+iiYYdF+vrNnXaQQRcwHJztc386usdMnk5FRRZNdoHrcJ2QOpSXvni14vfksECgYEA7KgbZx8oyudulQqzzus/m2hUudbPJbTa6w0P10QX1Efn2mEmWeg6p7bj3L+cN/OoOWZ4Ke0xKxkOQ0WYLLOvNVxRzF0Xjj+C57YHtShvbwe54IZFNrDe/FKHhpBYS6vFzKX6ck3HHEjC2WlqolAEHAbYFGTHobKBa0qgIvPl/UUCgYEAz6KmhQhNTOBIG/kY7MVHLfMQyvefhT+Xm/h73hJK3Nfp1M+QQZIj1uiDld6bEu7vUP91K6+JyE0BJZcP4eHCmzkp2KjcyGDWJQfbpaaH9RT0uiR7D7obBHf8BGOAgnA809SX8CD3cS+gCx8cqiIsMZB494A2iVyNiiufVIX1Zg8CgYEA0jHpzOjnEXkHRgewduuJnl3HSxyY9lOxUa5TUI6hX6HSM6uwJZDXcBlIP8xMU4Ht+7WgqxSKZE4n1eZdZ+7cgteRq6NPhb+xZF7Qb74PY52IIf0AQrhMBe7Dguh4FBXoZIFTdezRGbUio3o3BR1u2PnXOB3fFiZ3PrWUkBbzQsUCgYEApbd5E+AmYd73bmwHOqHRR5kho/yycpTomfFeW0VaPpyM4e6vgcXzmMiGjQzX0+qjUpAwoic93oGnEqtYX73hpiWfgm5zl/HBuFhnM/SPukl5cT9AgLLWcWCZ4Z7QqEqQIbkhcuO82bdbEsVICXmwr/ZQtai83jDiPo7GYZ1w2H0CgYEAjFxeHoUKnMO0GqOHy/v3gqS4357kTz7/PfXqbIO2OvyW5KX9MXeVJ6OD6KiqVGBGStF8Vy8VT/j2KXCjiJQ91gFqEJvkadsB1HXfXxPRwUYi3i7BaadpfnW3cM6uAE6iPVqhGu4Z0uyUp/koGgkBr/qbnwRgF9/hXb8Pygu0s5c=

生成的签名(sign)形式如下:

p0du54PYDnCdHjYFgc0mri5KqA09ziGVJZi/xaDiPzvePlMGGGHcBzH8qNV3pYvBxv60ezxC2a9ZM97bTkmJZt21mb5tBDPWBpYZsJqU3XCMgDnlisiq3Us2glwB9eHzVaZPTW14lSykkPEwLk8mwzQdwM5/SmCnHpAT+I3BOkBUPs6pl5i0eE/dA4ePAkXgWIqmsQbgh9As7Fgh65wScxbzsC8FJfbMhQDNV94WYCQd9cbkyOPidpnT9o7u2GoqkVjm2N73Ja2ObSdp5PQNbQ8UdtJ+PYBUTXDa7cLIkoc00JGSCqhGPA6rFCnNuAay/C4wtSKJzKZ5XpRWwYrkWQ==
  • 5、拼接成url
    把生成的签名赋值给 sign 参数,拼接到请求参数中,产生url,形式如下:
https://openapi.alipaydev.com/gateway.do?app_id=2016101800718925&biz_content={"out_trade_no":"20150519815610100992007","product_code":"FAST_INSTANT_TRADE_PAY","total_amount":88.88,"subject":"Iphone6 16G","body":"Iphone6 16G"}&charset=utf-8&format=json&method=alipay.trade.page.pay&notify_url="https://example.com/notify"&
return_url="https://example.com"sign_type=RSA2&timestamp=2020-10-1309:58:50&version=1.0&sign=p0du54PYDnCdHjYFgc0mri5KqA09ziGVJZi/xaDiPzvePlMGGGHcBzH8qNV3pYvBxv60ezxC2a9ZM97bTkmJZt21mb5tBDPWBpYZsJqU3XCMgDnlisiq3Us2glwB9eHzVaZPTW14lSykkPEwLk8mwzQdwM5/SmCnHpAT+I3BOkBUPs6pl5i0eE/dA4ePAkXgWIqmsQbgh9As7Fgh65wScxbzsC8FJfbMhQDNV94WYCQd9cbkyOPidpnT9o7u2GoqkVjm2N73Ja2ObSdp5PQNbQ8UdtJ+PYBUTXDa7cLIkoc00JGSCqhGPA6rFCnNuAay/C4wtSKJzKZ5XpRWwYrkWQ==

经过urlencode加密后实际形式如下:

app_id=2016102400749214&biz_content=%7B%22subject%22%3A%22%5Cu5929%5Cu5929%5Cu751f%5Cu9c9c2021041010554419%22%2C%22out_trade_no%22%3A%222021041010554419%22%2C%22total_amount%22%3A%2233.99%22%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay&notify_url=http%3A%2F%2F120.77.253.68%3A8000%2Fuser%2F&return_url=http%3A%2F%2F120.77.253.68%3A8000%2Fuser%2Forder%2F1&sign_type=RSA2&timestamp=2021-04-10+10%3A55%3A50&version=1.0&sign=cGeKkdf4tsZ18BvcfwbuclshF6XR9vAZYhGsJk%2BznaxGzl%2FsDPoG28e2ihw%2BS47rEJ64xGfTaT57ids7RckpsvOHseLUi5ziPu6VO38ogFvsrhXRceo2Jtp0TGJqaCAP4Chrpz2SD2j08GjH0RXUiK3I%2FX1IYxfN2lMbZfFi0SHqPv9eSoDVaYzXznYW%2BMcPX5zWfjPNsVXChRKIi8aeMKPjdGztDK9cxdbC7gol07EGRAcpAbNT73WTn7PPKqEnpV8o9vkIZ65L3mmupQmXkqsYoXE%2FlW4Y2P1IsZMg0diZxtQKOoQM5ObkRxFwbq%2BmrPE7uHvG2PEGRHN3S2mzOg%3D%3D
1.3 使用SDK自动签名

自动签名可以很方便的帮助我们实现签名操作。

class OrderPayView(View):
    '''支付'''

    def post(self, request):
        '''支付'''

        # 用户登录判断
        user = request.user
        if not user.is_authenticated():
            return JsonResponse({'res': 0, 'errmsg': '用户未登录'})


        # 获取订单id
        order_id = request.POST.get('order_id')

        # 校验参数
        if not order_id:
            return JsonResponse({'res': 1, 'errmsg': '无效订单'})

        try:
            order = OrderInfo.objects.get(order_id=order_id,
                                          user = user,
                                          pay_method = 3,
                                          order_status = 1,
                                          )
        except OrderInfo.DoesNotExist:
            return JsonResponse({'res': 2, 'errmsg': '订单错误'})

        # 调用支付宝API
        # 初始化支付对象
        app_private_key_string = open(os.path.join(settings.BASE_DIR, 'apps/order/app_private_key.pem')).read()
        alipay_public_key_string = open(os.path.join(settings.BASE_DIR, 'apps/order/alipay_public_key.pem')).read()
        alipay = AliPay(
            appid="2016102400749214", # 应用id
            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=True  # 默认False,使用沙箱环境时置为True
        )

        # 调用支付API
        total_pay = order.total_price+order.transit_price # Decimal 是不能序列化的,应转换字符串
        # 电脑网站支付
        try:
            # SDK封装的API会自动签名,省去了我们很多的功夫
            order_string = alipay.api_alipay_trade_page_pay(
                out_trade_no=order_id, # 订单id
                total_amount=str(total_pay), # 支付总金额
                subject='天天生鲜%s' % order_id,
                #return_url="https://example.com",
                return_url="http://120.77.253.68:8000/user/order/1", # 可选, 不填则使用默认notify url
                #notify_url="https://example.com/notify" # 可选, 不填则使用默认notify url
                notify_url="http://120.77.253.68:8000/user/" # 可选, 不填则使用默认notify url
            )
        except Exception as e:
            print(e)

        # 返回:提交支付页面url
        pay_url = 'https://openapi.alipaydev.com/gateway.do?' + order_string
        print(order_string)
        return JsonResponse({'res':3, 'pay_url':pay_url}) # 将支付地址回传给浏览器,从而引导用户跳转到支付页面

2 商户服务器验签

验证是否是支付宝服务器发来的请求。
商户服务器接收到支付宝的请求后,使用支付公钥解密

2.1 自行实现验签

普通公钥模式:
如当面付扫码支付获取二维码的返回内容为:

{"alipay_trade_precreate_response":{"code":"10000","msg":"Success","out_trade_no":"6141161365682511","qr_code":"https:\/\/qr.alipay.com\/bax03206ug0kulveltqc80a8"},"sign":"VrgnnGgRMNApB1QlNJimiOt5ocGn4a4pbXjdoqjHtnYMWPYGX9AS0ELt8YikVAl6LPfsD7hjSyGWGjwaAYJjzH1MH7B2/T3He0kLezuWHsikao2ktCjTrX0tmUfoMUBCxKGGuDHtmasQi4yAoDk+ux7og1J5tL49yWiiwgaJoBE="}

则待验签字段为:

{"code":"10000","msg":"Success","out_trade_no":"6141161365682511","qr_code":"https:\/\/qr.alipay.com\/bax03206ug0kulveltqc80a8"}

同时取出签名值 sign:

VrgnnGgRMNApB1QlNJimiOt5ocGn4a4pbXjdoqjHtnYMWPYGX9AS0ELt8YikVAl6LPfsD7hjSyGWGjwaAYJjzH1MH7B2/T3He0kLezuWHsikao2ktCjTrX0tmUfoMUBCxKGGuDHtmasQi4yAoDk+ux7og1J5tL49yWiiwgaJoBE=

调用验签函数:使用各自语言对应的 SHA256WithRSA (对应 sign_type 为 RSA2) 或 SHA1WithRSA (对应 sign_type 为 RSA) 签名验证函数,传入待验签字段、支付宝公钥、签名内容(sign),验签方法(signType)进行验签,根据返回结果判定是否验签通过(其原理:使用支付宝公钥加密待验签字段,并将结果与签名内容比较,如果相等则验签通过,说明请求是支付宝发送过来的)

2.2 使用SDK自动验签

支付宝SDK封装了验签功能,实现了自动验签
示法代码如下(支付宝返回页return_url和通知页notify_url的视图,使用自动验签):

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="2016091200490210",
            app_notify_url="http://115.159.122.64:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://115.159.122.64: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)

            existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
            for existed_order in existed_orders:
                existed_order.trade_no = trade_no
                existed_order.pay_time = datetime.now()
                existed_order.save()

            response = redirect("/index/#/app/home/member/order")
            # response.set_cookie("nextPath","pay", max_age=3)
            return response
        else:
            response = redirect("index")
            return response

    def post(self, request):
        """
        处理支付宝的notify_url
        """
        # 1. 先将sign剔除掉
        processed_dict = {}
        for key, value in request.POST.items():
            processed_dict[key] = value

        sign = processed_dict.pop("sign", None)

        # 2. 生成一个Alipay对象
        alipay = AliPay(
            appid="2016091200490210",
            app_notify_url="http://115.159.122.64:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://115.159.122.64:8000/alipay/return/"
        )

        # 3. 进行验签,确保这是支付宝给我们的
        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")
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值