暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

全网最详细的「支付宝沙箱环境」使用指南(看这一篇就够了)

Nephilim 2024-06-06
152

Tips:一些记录,一些笔记



2024/06/05

WEDNESDAY

Eat to life to live, but not for food.

人生应为生而食,不应为食而生。




01

支付宝沙箱环境


支付宝沙箱环境「官方文档」:

https://opendocs.alipay.com/common/02kkv7?pathHash=8fd32ef6


开发文档:

https://opendocs.alipay.com/open/01bxlm


服务端开发文档:

https://opendocs.alipay.com/open/00a0ut?pathHash=b19b288a


服务端开发文档「电脑网站支付」:

https://opendocs.alipay.com/open/270/105898?pathHash=b3b2b667


开发工具:

https://opendocs.alipay.com/open/009ys9


沙箱应用:

https://open.alipay.com/develop/sandbox/app


沙箱工具:

https://open.alipay.com/develop/sandbox/tool/alipayclint


沙箱账号:

https://open.alipay.com/develop/sandbox/account


——————————————————

沙箱账号是支付宝自动分配的,其中的余额都是假的,你可以随意充值或提现,都只是数字的变化,用于测试你开发的网站或应用的「支付功能及其相关逻辑」是否运行正常:


而在开发中用到最多的诸如API的接口等等信息,都是通过「沙箱应用」提供出来的:


02

Django项目中使用「支付宝沙箱环境」


上面大概介绍了下支付宝的沙箱环境,下面在Django项目中使用它。

沙箱环境的配置项,我们集中到Django的全局配置文件「settings.py中。


文件「settings.py」

    # ======================================
    # 支付宝沙箱环境
    # ======================================


    # -----------------------
    # 支付宝网关地址
    # 支付宝沙箱网关地址,开发者在沙箱环境调用 OpenAPI 发送 http(s) 请求的目标地址,需配置在AlipayClient中。
    ALIPAY_SERVER_URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"


    # -----------------------
    # APPID
    ALIPAY_APP_ID = "9021000137680887"


    # -----------------------
    # 接口加签方式:「公钥模式 - 私钥」
    ALIPAY_APP_PRIVATE_KEY = "MIIEpQIBAAKCAQEAu26zZ9ufwCpCxf88IVIEhz4jGsMTfnt9X1HNjJ/mog8KBTgE9PwxW7ehVYgAgkNP3jpMqSQzfouRNYeqhgbgNFCtE6CpFRSmapH8X0//dZ0i2AIX1xxGATwNB4KlzAohKQMLc4DHMMfBbptuVGdzIRl1o/m4PYDS9Fs6ko3diJNTKzA+L9SS/DZLQH6tsyp7RdstQ/LPo5/1Zol03+jShioRreHwmmI0PK3JjsYOb5arH+3tCVRf3lL3fWKavmT9Q2lFgZQmTg9C/466bRNoLYWeIV/Z9c0T9br3Mmj1/b7hbgz/pLWvvGrhVYvrsyrc2nLRrRZJ6SLt1Z7bcd6DmwIDAQABAoIBAASNk94Z/69zV1vn71X/4AxI/iFQS9DuvwTpv8yCtd+J7fxid/xbg/duZoTlYN3JNM95KxOQxY91ZHmgagwHbrrWmt23MWRJtCEymqbWEqaQwp/srReUoD63brmX+t8R/+w3HrgXkL8mmxRMwklamflb3Pe2wTr9PiKhdLLKr2752ozjwi/DBV1Pei8wKVjzYwYkRfAKFytmmlgFnl1n2BQStXDKAcuA9rJnuMVF4D5ZRVVuuR04vNMLJWyzbN/az8+Tvd/JIWD0svCO5IiXZKYkQ0pD1VCZNOe1hG7RFq79+15wADjrq7ztH6+BYmNed+gevq/htfseBtPdiuWHtIECgYEA4613083+8JmSrgeESheg4tZwG7ouhO/VCINfSAy2QUERE24yanqvKKb2ZJpUUTC3CqjYfjpJJnGWfqKVSpg72ObCWjMH96BBzX+xCVC2n8nb6cs9vwC8c3t3RZ55U86sABzYd3s6RSog3xPefN+bqGmfLNxRWDat61+PaiwNWHMCgYEA0r+ZsJHhrJ+b+g9vXkhxzO7jVk/fbyA2D9eXx/85Z3lkpjV7pq9WEXmrTkhHr7OyAnFJH7O7IvKHpzDbNeSaZt9L/gk9hdIhvLG9/6qGZHGqwrlUV/5OxuxeCu5ihyfkMF2HFDwnDJDdNPgtxYoYU4rFr1Pr4XvIUKEYpYW4ZjkCgYEAiBIki3GR7DnccCYN6jo07V07o1aidtDfgTcCuwzd4CIDEpnJh9nK1YDzwSx7OArlWnmO6eHr1g4M46JHl01kdz1y6nWVTWl56ldmdyrF+rmptRDC1mSaPGqN3g3YdT4PjgGvXblIGg4Tlz9IuVjqrrxxZV5HP3FugD2ceNfbIgECgYEAsJ0VJV376/iOdI8+25qGlCGWrxycuLob1Ebu0hJXfxnCeW/UwimqTVbTcvUr7lj/UwCxMTokF/N8L2bILm/33UJAoxIHVwB0eXfTLFEx8zc2zZ0Wy+85AYfQgASq2W1a1BVtQE3ww50bZGqfoS5LmKdxZ69CvGJJyxhWWVCo6jECgYEAqk8+cx2pnvF89zTkGXvvuC7898/ZyNBHnSNGO6dLRZE9heiEfvmjqDgVNoxDhQpObMxfv4HdBtHqd9gCF7Oxj4TbicCwaqytDtlLyQs3Ia7SGZ+U2PRf9kqfbm9OakI8UONDQ9JhMjuY7jspaoROjv+X5geHJDsnw1/5BGeu18Q="


    # -----------------------
    # 接口加签方式:「公钥模式 - 支付宝公钥」
    ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Zt8CyUbB+W+zk6a6zCFMFpf2QX4RGwDke9fg5WX9A4SiDffw9Q+M+wcclJglHO5/5v0DR0LcUz8MF0wVK4za/iP0YWFCRWy4vVRPfDVDkqOXaIr4cDr7e1q7ayX1y2LA0TQAYMWXdOEbCyehaJU1+C8Mgs/G0JnwSXuZ4h/BBprnfzG3/Lc6pAIwhH3KbiMaEZIRi8j0AlUIsoDZf834SDm7LusluhY3chG0vQI5BjbgWzVmoNB3XSenoJNhqjcoaC9abz9DWJkftr6Z92DMPFqF//AjEsDpQGWF2froRbXZOqHAumtDi5zm+xrP0AGsMUF5kwrEoGw2DNEyxGfCQIDAQAB"



    03

    支付宝的支付逻辑

    官方文档地址:

    https://opendocs.alipay.com/open/270/105899?pathHash=d57664bf


    时序图:

    调用流程如下:

    1. 商家系统调用 alipay.trade.page.pay(统一收单下单并支付页面接口)向支付宝发起支付请求,支付宝对商家请求参数进行校验,而后重新定向至用户登录页面。

    2. 用户确认支付后,支付宝通过 get 请求 returnUrl(商户入参传入),返回同步返回参数。

    3. 交易成功后,支付宝通过 post 请求 notifyUrl(商户入参传入),返回异步通知参数。

    4. 若由于网络等原因,导致商家系统没有收到异步通知,商家可自行调用 alipay.trade.query(统一收单交易查询接口)查询交易以及支付信息(商家也可以直接调用该查询接口,不需要依赖异步通知)。

    注意: 

    • 由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。

    • 商家系统接收到异步通知以后,必须通过验签(验证通知中的 sign 参数)来确保支付通知是由支付宝发送的。详细验签规则可查看 异步通知验签。

    • 接收到异步通知并验签通过后,请务必核对通知中的 app_id、out_trade_no、total_amount 等参数值是否与请求中的一致,并根据 trade_status 进行后续业务处理。

    • 在支付宝端,partnerId 与 out_trade_no 唯一对应一笔单据,商家端保证不同次支付 out_trade_no 不可重复;若重复,支付宝会关联到原单据,基本信息一致的情况下会以原单据为准进行支付。


    04

    实现一个支付功能的「样例」


    官方示例文档:

    https://opendocs.alipay.com/open/02no41?pathHash=2faf7eac


    首先,通过PIP安装支付沙箱的PyPI依赖包「pip install alipay-sdk-python」:

    https://pypi.org/project/alipay-sdk-python/

    代码如下:

      (venv) (base) adamhuan@Leviathan django_daily_media % pip install alipay-sdk-python
      Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
      Collecting alipay-sdk-python
      Downloading https://mirrors.aliyun.com/pypi/packages/78/c2/517f8f9a9714b742f8cb1040e45ac7961ebd0d9f02ca3709ac38d48616a5/alipay_sdk_python-3.7.137-py3-none-any.whl (22.0 MB)
      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 22.0/22.0 MB 344.4 kB/s eta 0:00:00
      Collecting pycryptodome (from alipay-sdk-python)
      Downloading https://mirrors.aliyun.com/pypi/packages/ff/96/b0d494defb3346378086848a8ece5ddfd138a66c4a05e038fca873b2518c/pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl (2.4 MB)
      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 342.1 kB/s eta 0:00:00
      Collecting rsa (from alipay-sdk-python)
      Downloading https://mirrors.aliyun.com/pypi/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl (34 kB)
      Collecting pyasn1>=0.1.3 (from rsa->alipay-sdk-python)
      Downloading https://mirrors.aliyun.com/pypi/packages/23/7e/5f50d07d5e70a2addbccd90ac2950f81d1edd0783630651d9268d7f1db49/pyasn1-0.6.0-py2.py3-none-any.whl (85 kB)
      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 85.3/85.3 kB 352.0 kB/s eta 0:00:00
      Installing collected packages: pycryptodome, pyasn1, rsa, alipay-sdk-python
      Successfully installed alipay-sdk-python-3.7.137 pyasn1-0.6.0 pycryptodome-3.20.0 rsa-4.9
      (venv) (base) adamhuan@Leviathan django_daily_media %



      该依赖包的PyPI页面下有给出一个官方样例:

      这段代码需要注意的是,前面我们在时序图中可以看到,使用的接口是「alipay.trade.page.pay」

      因此,在这段代码中,接口样例中,只需要选择对应的接口代码即可,而不需要全部选择。


      关于接口「alipay.trade.page.pay」官方的接口文档是:

      https://opendocs.alipay.com/open/028r8t?scene=22


      最终,我们本地的样例代码如下所示:

        #!/usr/bin/env python
        # -*- coding: UTF-8 -*-


        # ——————————————————
        # 脚本说明:
        # xxxxxxxxx
        # ——————————————————


        # ========================================
        # 开始
        # ))))))))) 模块包导入
        import logging
        import traceback


        import os
        import sys


        sys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')


        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')
        from django.conf import settings




        from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
        from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
        from alipay.aop.api.FileItem import FileItem
        from alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModel
        from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
        from alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModel
        from alipay.aop.api.domain.GoodsDetail import GoodsDetail
        from alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfo
        from alipay.aop.api.domain.SettleInfo import SettleInfo
        from alipay.aop.api.domain.SubMerchant import SubMerchant
        from alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequest
        from alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequest
        from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
        from alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequest
        from alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponse
        from alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse


        # ))))))))) 日志
        logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s %(levelname)s %(message)s',
        filemode='a',)
        logger = logging.getLogger('')


        # ))))))))) 类名


        # ))))))))) 执行阶段
        if __name__ == '__main__':
        """
        设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。
        """
        alipay_client_config = AlipayClientConfig()


        # =================
        # Default
        # =================


        # 旧版本的API:openapi.alipay.com
        # 新版本的API:openapi-sandbox.dl.alipaydev.com
        # alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'


        # alipay_client_config.app_id = '[your app_id]'
        # alipay_client_config.app_private_key = '[your app private key]'
        # alipay_client_config.alipay_public_key = '[alipay public key]'


        # =================
        # For Django Custom
        # =================


        alipay_client_config.server_url = settings.ALIPAY_SERVER_URL
        alipay_client_config.app_id = settings.ALIPAY_APP_ID
        alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY
        alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY


        # 输出
        print("")
        print("==========================================")
        print(settings.ALIPAY_SERVER_URL)


        """
        得到客户端对象。
        注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。
        logger参数用于打印日志,不传则不打印,建议传递。
        """
        client = DefaultAlipayClient(alipay_client_config=alipay_client_config, logger=logger)


        """
        页面接口示例:alipay.trade.page.pay
        「选择需要的接口」
        """
        # 对照接口文档,构造请求对象
        model = AlipayTradePagePayModel()


        # 请求参数:必选
        model.out_trade_no = "pay201805020000226"
        model.total_amount = 50
        model.subject = "测试"
        model.body = "支付宝测试"
        model.product_code = "FAST_INSTANT_TRADE_PAY"


        # 请求参数:可选
        # settle_detail_info = SettleDetailInfo()
        # settle_detail_info.amount = 50
        # settle_detail_info.trans_in_type = "userId"
        # settle_detail_info.trans_in = "2088302300165604"
        # settle_detail_infos = list()
        # settle_detail_infos.append(settle_detail_info)
        # settle_info = SettleInfo()
        # settle_info.settle_detail_infos = settle_detail_infos
        # model.settle_info = settle_info
        # sub_merchant = SubMerchant()
        # sub_merchant.merchant_id = "2088301300153242"
        # model.sub_merchant = sub_merchant


        # 构造的请求
        request = AlipayTradePagePayRequest(biz_model=model)


        # 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段
        response = client.page_execute(request, http_method="GET")


        # 输出
        print("")
        print("==========================================")
        print("alipay.trade.page.pay response:" + response)


        # ))))))))) 结束


        # ========================================
        # 结束



        以命令行的方式运行:

        运行结果:

          /Users/adamhuan/PycharmProjects/django_daily_media/venv/bin/python Users/adamhuan/PycharmProjects/django_daily_media/utils/alipay_example.py


          ==========================================
          https://openapi-sandbox.dl.alipaydev.com/gateway.do
          2024-06-05 12:18:39,853 INFO [afc3b990-22f2-11ef-8ece-f2557eb4cbe8]request:https://openapi-sandbox.dl.alipaydev.com/gateway.do?app_id=9021000137680887&biz_content={"body":"支付宝测试","out_trade_no":"pay201805020000226","product_code":"FAST_INSTANT_TRADE_PAY","subject":"测试","total_amount":50}&charset=utf-8&format=json&method=alipay.trade.page.pay&sign_type=RSA2&timestamp=2024-06-05 12:18:39&version=1.0&sign=HCM1xQTmZOyPx31tH9MIYM+Q5eTDtopBRWmTIeHbc2vcjFrEDPe8rW3010N1Z/EpsMvzCDwMLkJqErTOMtefB+NKnL0EGRQUFlM1gut575nTX9qXHLCC+ySRSL8lgftLAgNaOR1JtGcewBgQnMRTUJk5TGDOFYNMA7ANTKgthbXFmTOt3Zieki+awT0B+NQGmckHx1OqCrjY8lsge1yPkhrHUa82djw/AtdoEKNee2CnCYZ1+wSkQb2aM0zS6Idf60wSWfj9KnSEO5fjLeSdgogLiEHt7rSXpOWVfBK6iYT8H+VACek56fGHEZrVt0aEV2d2g0OOpLlk1dmgyfFoHA==


          ==========================================
          alipay.trade.page.pay response:https://openapi-sandbox.dl.alipaydev.com/gateway.do?timestamp=2024-06-05+12%3A18%3A39&app_id=9021000137680887&method=alipay.trade.page.pay&charset=utf-8&format=json&version=1.0&sign_type=RSA2&sign=HCM1xQTmZOyPx31tH9MIYM%2BQ5eTDtopBRWmTIeHbc2vcjFrEDPe8rW3010N1Z%2FEpsMvzCDwMLkJqErTOMtefB%2BNKnL0EGRQUFlM1gut575nTX9qXHLCC%2BySRSL8lgftLAgNaOR1JtGcewBgQnMRTUJk5TGDOFYNMA7ANTKgthbXFmTOt3Zieki%2BawT0B%2BNQGmckHx1OqCrjY8lsge1yPkhrHUa82djw%2FAtdoEKNee2CnCYZ1%2BwSkQb2aM0zS6Idf60wSWfj9KnSEO5fjLeSdgogLiEHt7rSXpOWVfBK6iYT8H%2BVACek56fGHEZrVt0aEV2d2g0OOpLlk1dmgyfFoHA%3D%3D&biz_content=%7B%22body%22%3A%22%E6%94%AF%E4%BB%98%E5%AE%9D%E6%B5%8B%E8%AF%95%22%2C%22out_trade_no%22%3A%22pay201805020000226%22%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%2C%22subject%22%3A%22%E6%B5%8B%E8%AF%95%22%2C%22total_amount%22%3A50%7D


          Process finished with exit code 0



          然后,在浏览器中打开最后输出的链接「alipay.trade.page.pay response」


          注意,上面的商家信息与买家信息都是之前在「沙箱环境」的「沙箱账户」中提供的。


          它们之前的状态:


          在上述虚拟交易完成后的状态:

          可以非常明确得看到:

          • 商家多了50

          • 买家少了50


          如果接口返回的链接你已经执行了「交易动作」,再次访问的时候会遇到错误:

          至此,一个简单的支付宝「沙箱环境」的「交易功能」的演示,就完成了。


          ——————————————————

          为了让上述代码可以更方便的在Django中运行,我们对其进行修改,修改成「类」的方式:

            #!/usr/bin/env python
            # -*- coding: UTF-8 -*-


            # ——————————————————
            # 脚本说明:
            # 支付方法:
            # 使用「支付宝」的「沙箱环境」
            # ——————————————————


            # ========================================
            # 开始
            # ))))))))) 模块包导入
            import logging
            import traceback


            # 『单独运行』使用
            # 如果「settings」来自Django,需要社会环境变量
            # import os
            # import sys
            # sys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')
            # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')


            # 「结合Django」使用
            from django.conf import settings




            from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
            from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
            from alipay.aop.api.FileItem import FileItem
            from alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModel
            from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
            from alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModel
            from alipay.aop.api.domain.GoodsDetail import GoodsDetail
            from alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfo
            from alipay.aop.api.domain.SettleInfo import SettleInfo
            from alipay.aop.api.domain.SubMerchant import SubMerchant
            from alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequest
            from alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequest
            from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
            from alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequest
            from alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponse
            from alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse


            # ))))))))) 日志
            logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s %(levelname)s %(message)s',
            filemode='a',)
            logger = logging.getLogger('')


            # ))))))))) 类名


            # ))))))))) 执行阶段
            class Alipay():
            """
            设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。
            """
            alipay_client_config = AlipayClientConfig()


            # =================
            # Default
            # =================


            # 旧版本的API:openapi.alipay.com
            # 新版本的API:openapi-sandbox.dl.alipaydev.com
            # alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'


            # alipay_client_config.app_id = '[your app_id]'
            # alipay_client_config.app_private_key = '[your app private key]'
            # alipay_client_config.alipay_public_key = '[alipay public key]'


            # =================
            # For Django Custom
            # =================


            alipay_client_config.server_url = settings.ALIPAY_SERVER_URL
            alipay_client_config.app_id = settings.ALIPAY_APP_ID
            alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY
            alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY


            # 输出
            print("")
            print("==========================================")
            print(settings.ALIPAY_SERVER_URL)


            def __init__(self):
            """
            得到客户端对象。
            注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。
            logger参数用于打印日志,不传则不打印,建议传递。
            """
            self.client = DefaultAlipayClient(
            alipay_client_config=self.alipay_client_config,
            logger=logger
            )


            def trade_page(self,out_trade_no,total_amount,subject,body,product_code):
            """
            页面接口示例:alipay.trade.page.pay
            「选择需要的接口」
            """
            # 对照接口文档,构造请求对象
            model = AlipayTradePagePayModel()


            # 请求参数:必选
            # model.out_trade_no = "pay202406051355202"
            # model.total_amount = 50
            # model.subject = "测试"
            # model.body = "支付宝测试"
            # model.product_code = "FAST_INSTANT_TRADE_PAY"


            model.out_trade_no = out_trade_no
            model.total_amount = total_amount
            model.subject = subject
            model.body = body
            model.product_code = product_code


            # 请求参数:可选
            # settle_detail_info = SettleDetailInfo()
            # settle_detail_info.amount = 50
            # settle_detail_info.trans_in_type = "userId"
            # settle_detail_info.trans_in = "2088302300165604"
            # settle_detail_infos = list()
            # settle_detail_infos.append(settle_detail_info)
            # settle_info = SettleInfo()
            # settle_info.settle_detail_infos = settle_detail_infos
            # model.settle_info = settle_info
            # sub_merchant = SubMerchant()
            # sub_merchant.merchant_id = "2088301300153242"
            # model.sub_merchant = sub_merchant


            # 构造的请求
            request = AlipayTradePagePayRequest(biz_model=model)


            # 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段
            response = self.client.page_execute(request, http_method="GET")


            # 输出
            # print("")
            # print("==========================================")
            # print("alipay.trade.page.pay response:" + response)


            # 返回阶段
            return response


            # ))))))))) 结束


            # ========================================
            # 结束



            在我的项目中,它位于:


            05

            Django中定义「订单」相关的数据模型

            上面只是一个官方例子的演示,为了在Django中更好的使用到支付宝的沙箱环境,我们需要定义一个「订单表」以及与其相关的数据逻辑,具体如下。


            数据模型类「models.py」

              from django.db import models


              from account.models import Profile


              # Create your models here.


              class Card(models.Model):
              card_name = models.CharField(
              verbose_name="会员卡名称",
              max_length=400,
              unique=True,
              )
              card_price = models.DecimalField(
              verbose_name="价格",
              max_digits=8,
              decimal_places=2,
              )
              duration = models.IntegerField(
              verbose_name="有效天数",
              )
              info = models.CharField(
              verbose_name="简介",
              max_length=200,
              null=True,
              blank=True,
              )
              color = models.CharField(
              verbose_name="颜色",
              max_length=200,
              null=True,
              blank=True,
              )
              created_at = models.DateTimeField(
              verbose_name="创建时间",
              auto_now_add=True,
              editable=True,
              )
              updated_at = models.DateTimeField(
              verbose_name="更新时间",
              auto_now=True,
              editable=True,
              )


              class Meta:
              verbose_name = "会员卡信息"
              verbose_name_plural = verbose_name


              def __str__(self):
              return self.card_name


              class Order(models.Model):
              ORDER_STATUS = (
              ("TRADE_SUCCESS", "成功"),
              ("TRADE_CLOSED", "超时关闭"),
              ("WAIT_BUYER_PAY", "交易创建"),
              ("TRADE_FINISHED", "交易结束"),
              ("PAYING", "待支付"),
              )
              PAY_TYPE = (
              ("alipay", "支付宝"),
              ("wechat", "微信"),
              )


              # 用户
              user = models.ForeignKey(
              verbose_name="用户",
              to=Profile,
              related_name='orders',
              to_field='uid',
              on_delete=models.CASCADE,
              )


              # 禁止反向
              card = models.ForeignKey(
              verbose_name="会员卡",
              to=Card,
              related_name='+',
              on_delete=models.DO_NOTHING,
              )


              # 订单号(唯一)
              order_sn = models.CharField(
              verbose_name="订单编号",
              max_length=30,
              null=True,
              blank=True,
              unique=True,
              )


              # 支付宝交易号
              trade_no = models.CharField(
              verbose_name="支付宝交易号",
              max_length=100,
              null=True,
              blank=True,
              unique=True,
              )


              # 支付状态
              pay_status = models.CharField(
              verbose_name="订单状态",
              choices=ORDER_STATUS,
              default="PAYING",
              max_length=30,
              )


              # 支付类型
              pay_type = models.CharField(
              verbose_name="支付类型",
              choices=PAY_TYPE,
              default='alipay',
              max_length=10,
              )


              order_mount = models.DecimalField(
              verbose_name="订单金额",
              max_digits=10,
              decimal_places=2,
              )


              pay_time = models.DateTimeField(
              verbose_name="支付时间",
              null=True,
              blank=True,
              )


              # 创建时间
              created_at = models.DateTimeField(
              verbose_name="创建时间",
              auto_now_add=True,
              editable=True,
              )


              # 更新时间
              updated_at = models.DateTimeField(
              verbose_name="更新时间",
              auto_now=True,
              editable=True,
              )


              class Meta:
              verbose_name="订单信息"
              verbose_name_plural=verbose_name


              def __str__(self):
              return self.order_sn



              序列化「serializers.py」

                #!/usr/bin/env python
                # -*- coding: UTF-8 -*-


                # ——————————————————
                # 脚本说明:
                # xxxxxxxxx
                # ——————————————————


                # ========================================
                # 开始
                # ))))))))) 模块包导入
                from rest_framework import serializers


                from trade.models import *


                # ))))))))) 类名


                class CardSerializer(serializers.ModelSerializer):


                class Meta:
                model = Card
                fields = '__all__'
                # fields = [
                # 'card_name',
                # 'card_price',
                # 'info',
                # 'color',
                # 'duration',
                # ]


                class OrderSerializer(serializers.ModelSerializer):
                class Meta:
                model = Order
                fields = '__all__'


                # ))))))))) 执行阶段


                # ))))))))) 结束


                # ========================================
                # 结束



                权限「permissions.py」

                  #!/usr/bin/env python
                  # -*- coding: UTF-8 -*-


                  # ——————————————————
                  # 脚本说明:
                  # xxxxxxxxx
                  # ——————————————————


                  # ========================================
                  # 开始
                  # ))))))))) 模块包导入
                  from rest_framework import permissions


                  # ))))))))) 类名
                  class IsAdminUserOrReadonly(permissions.BasePermission):
                  def has_permission(self, request, view):
                  if request.method in permissions.SAFE_METHODS:
                  return True
                  return request.user.is_supperuser


                  # ))))))))) 执行阶段


                  # ))))))))) 结束


                  # ========================================
                  # 结束



                  Django管理类「admin.py」

                    from django.contrib import admin


                    from trade.models import *


                    # Register your models here.


                    @admin.register(Card)
                    class CardAdmin(admin.ModelAdmin):
                    list_display = [
                    'card_name',
                    'card_price',
                    'duration',
                    'info',
                    'color',
                    'created_at',
                    'updated_at',
                    ]


                    @admin.register(Order)
                    class OrderAdmin(admin.ModelAdmin):
                    list_display = [
                    'user',
                    'card',
                    'order_sn',
                    'trade_no',
                    'pay_status',
                    'pay_type',
                    'order_mount',
                    'pay_time',
                    'created_at',
                    'updated_at',
                    ]


                    06

                    Django中的支付流程设计


                    在我当前的Django项目中,支付流程的起点是「会员卡」管理页面:

                    当点击了具体的会员卡的卡片后,会触发对应的点击事件,进而调用触发的方法,跳转到支付宝沙箱环境的支付页面。

                    而跳转「支付页面」之前,我们应该构造好支付接口所需要的相关参数:

                    因此,在Django中需要配置新的路由,以关联具体的处理函数。


                    Django全局路由文件「urls.py」

                      """
                      URL configuration for django_daily_media project.


                      The `urlpatterns` list routes URLs to views. For more information please see:
                      https://docs.djangoproject.com/en/5.0/topics/http/urls/
                      Examples:
                      Function views
                      1. Add an import: from my_app import views
                      2. Add a URL to urlpatterns: path('', views.home, name='home')
                      Class-based views
                      1. Add an import: from other_app.views import Home
                      2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
                      Including another URLconf
                      1. Import the include() function: from django.urls import include, path
                      2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
                      """
                      from django.contrib import admin
                      from django.urls import path, include


                      from rest_framework.routers import DefaultRouter


                      # from movie.views import *
                      # from account.views import *


                      from movie import views as movie_view
                      from account import views as account_view
                      from trade import views as trade_view


                      # 这里注册的地址,在后期访问的时候,都需要加上前缀「/api/xxxx」
                      router = DefaultRouter()
                      router.register(
                      'movie', movie_view.MovieViewSet, 'movie'
                      )
                      router.register(
                      'category', movie_view.CategoryViewSet, 'category'
                      )
                      router.register(
                      'collects', account_view.CollectViewSet, 'collects'
                      )
                      router.register(
                      'card', trade_view.CardViewSet, 'card'
                      )
                      router.register(
                      'order', trade_view.OrderViewset, 'order'
                      )


                      urlpatterns = [


                      # Django Admin
                      path("admin/", admin.site.urls),


                      # Django Rest Framework
                      path("api/", include(router.urls)),


                      # Django Djoser
                      path('auth/', include('djoser.urls')),


                      # Django Token Based Authentication
                      # path('auth/', include('djoser.urls.authtoken')),


                      # Django Djoser SWT
                      path("jwt/", include("djoser.urls.jwt")),


                      # Account: Activation
                      path("activate/<str:uid>/<str:token>/", account_view.account_activate),


                      # Account: Password Reset
                      # path("password_reset/<str:uid>/<str:token>/", account_password_reset),


                      # 支付博快:Alipay
                      path("api/alipay/", trade_view.AlipayAPIView.as_view()),


                      ]



                      从上面代码可以看到,因为与支付相关,所以新增的路由「/api/alipay」的对应视图设置在了「trade」应用中:


                      接下来是「trade」中对应视图的设计,它的完整代码如下:

                        from django.shortcuts import render
                        from django.utils import timezone
                        from django_filters import rest_framework as filters


                        from datetime import datetime


                        from rest_framework import viewsets,status
                        from rest_framework.views import APIView
                        from rest_framework.response import Response
                        from rest_framework import pagination


                        from trade.models import *
                        from trade.serializers import *
                        from trade.permissions import *


                        from account.models import *


                        from utils.error import *
                        from utils.common import *
                        from utils.payment_alipay import *




                        # Create your views here.


                        # Card


                        class CardFilter(filters.FilterSet):
                        card_name = filters.CharFilter(lookup_expr='icontains')
                        duration = filters.NumberFilter()


                        class Meta:
                        model = Card
                        fields = ['card_name', 'duration']


                        class CardPagination(pagination.PageNumberPagination):
                        page_size = 12
                        max_page_size = 20
                        page_size_query_param = 'page_size'


                        class CardViewSet(viewsets.ModelViewSet):
                        queryset = Card.objects.all()
                        serializer_class = CardSerializer
                        filter_backends = (filters.DjangoFilterBackend,)
                        filterset_class = CardFilter
                        pagination_class = CardPagination


                        # permission_classes = [
                        # IsAdminUserOrReadonly,
                        # ]


                        # Order


                        class OrderFilter(filters.FilterSet):
                        pay_status = filters.CharFilter(lookup_expr='icontains')
                        pay_type = filters.CharFilter(lookup_expr='icontains')


                        class Meta:
                        model = Order
                        fields = ['pay_status', 'pay_type']


                        class OrderPagination(pagination.PageNumberPagination):
                        page_size = 12
                        max_page_size = 20
                        page_size_query_param = 'page_size'


                        class OrderViewset(viewsets.ModelViewSet):
                        queryset = Order.objects.all()
                        serializer_class = OrderSerializer
                        filter_backends = (filters.DjangoFilterBackend,)
                        filterset_class = OrderFilter
                        pagination_class = OrderPagination


                        permission_classes = [
                        IsAdminUserOrReadonly,
                        ]


                        # Alipay


                        class AlipayAPIView(APIView):


                        # 发起支付宝支付「场景」:
                        # 1. 创建新的支付
                        # 2. 支付未完成的订单
                        def get(self, request):


                        # 判断卡号「card_id」是否正确
                        card_id = request.GET.get("card_id", None)


                        print("================")
                        print(card_id)


                        try:
                        card = Card.objects.get(id=card_id)
                        except:
                        return Response(
                        response_data(
                        *TradeError.CardParamError
                        )
                        )


                        # 订单号
                        out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)


                        # 创建订单
                        try:
                        Order.objects.create(
                        user = Profile.objects.get(user=request.user),
                        card = card,
                        order_sn = out_trade_no,
                        order_mount = card.card_price,
                        pay_time = timezone.now(),
                        )
                        except:
                        return Response(
                        response_data(
                        *TradeError.OrderCreateError
                        )
                        )


                        # 请求支付宝
                        try:
                        alipay = Alipay()
                        pay_url = alipay.trade_page(
                        out_trade_no=out_trade_no,
                        total_amount=str(card.card_price),
                        subject=card.card_name,
                        body="「支付宝 - 沙箱环境」支付",
                        product_code="FAST_INSTANT_TRADE_PAY"
                        )
                        return Response(pay_url)
                        except:
                        return Response(
                        response_data(
                        *TradeError.AlipayRequestError
                        )
                        )


                        至此,Django后端的接口就集成完成了。

                        下面,还有Vue3前端代码,实现支付的前端页面。


                        07

                        Vue.JS前端页面


                        在前端页面的会员卡页面中,我是通过自定义组件完成的三张会员卡的渲染的:


                        而页面点击是通过子组件暴露出来的事件,最终传递到子组件内部进行事件处理的「@child-event」


                        它在子组件中的定义是:

                        因此,前端调用后端的「沙箱环境」的代码将写在这一部分。


                        它的完整代码如下所示:

                          <template>
                          <a-badge-ribbon :text="card.card_price + ' RMB/年'" color="cyan" :style="{top: '15px', right: '2px'}">
                          <div class="card-container">
                          <div class="card" @click="emitChildEvent">
                          <!--
                          <div class="card-header">
                          <h3>{{ title }}</h3>
                          </div>
                          -->
                          <div class="card-body">
                          <div class="level-badge" :class="levelClass">
                          <!-- <span style="color: white;font-weight: bold">{{ level }}</span> -->
                          {{ card.card_name }}
                          </div>
                          <h2 style="color: black">
                          <slot></slot>
                          </h2>
                          </div>
                          </div>
                          </div>
                          </a-badge-ribbon>
                          </template>


                          <script>
                          import axios from 'axios'


                          export default {
                          name: 'AdvancedCard',
                          props: {
                          // title: String,
                          // level: String,
                          // color: String,
                          // price: String,
                          card: '',
                          },
                          computed: {
                          levelClass() {
                          const levelToClassMap = {
                          '黄金VIP': 'low-level',
                          '白金VIP': 'mid-level',
                          '星钻VIP': 'high-level'
                          };
                          return levelToClassMap[this.card.card_name] || 'low-level';
                          },
                          },
                          methods: {
                          // 将子组件的事件传递到父组件
                          emitChildEvent() {
                          const message = "选择的等级「" + this.level + "」"


                          console.log(this.card)
                          console.log(this.card.id)


                          axios
                          // .get('/api/alipay', {card_id: this.card.id})
                          .get('/api/alipay/?card_id=' + this.card.id)
                          .then(
                          response => {
                          console.log(response.data)


                          // 跳转到支付页面
                          window.location.href = response.data
                          }
                          )


                          this.$emit('child-event', message)
                          }
                          }
                          };
                          </script>


                          <style scoped>
                          .card-container {
                          display: flex;
                          justify-content: center;
                          }


                          .card {
                          width: 300px;
                          margin: 10px;
                          padding: 10px;
                          background-color: #f0f0f0;
                          border: 1px solid #ddd;
                          border-radius: 4px;
                          }
                          .card:hover {
                          box-shadow: 0 8px 16px 0 rgba(0,0,0,0.8);
                          }


                          .card-header {
                          background-color: #42b983;
                          color: white;
                          padding: 5px;
                          border-radius: 4px 4px 0 0;
                          }


                          .card-body {
                          padding: 10px;
                          }


                          .level-badge {
                          padding: 2px 5px;
                          border-radius: 4px;
                          margin-bottom: 5px;
                          text-align: center;
                          }


                          .low-level {
                          background-color: gold;
                          color: white;
                          font-weight: bold;
                          }


                          .mid-level {
                          background-color: lightcoral;
                          color: white;
                          font-weight: bold;
                          }


                          .high-level {
                          background-color: rebeccapurple;
                          color: white;
                          font-weight: bold;
                          }
                          </style>


                          ——————————————————

                          以上,步骤如果都完成了,那么你就实现了:

                          在会员卡的界面点击具体的会员卡并跳转支付宝的沙箱支付的前后端流程。


                          但目前依旧存在的问题是,当你跳转支付界面,支付成功后,并不能自动的回到Django项目的页面,并且,当前的Django项目无法得到来自支付宝反馈的支付成功或失败的信息。


                          如上,支付成功后,无法自动跳转回来。


                          08

                          内网穿透

                          支付宝的支付消息反馈回来的时候,需要有公网地址。


                          但公网的IP地址与服务器资源通常不是免费的,并且操作起来肯定没有本地调试方便。


                          因此,为了让公网用户可以访问本地资源,你需要「内网穿透」的解决方案。


                          市面上有很多的内网穿透的产品与解决方案,百度、谷歌,可以找到一大堆。


                          这里,使用的是「cpolar」。


                          ——————————————————

                          CPOLAR官方网站:

                          https://www.cpolar.com/


                          官方下载地址:

                          https://www.cpolar.com/download


                          虽然收费,但免费的权限已经够用:

                          当你注册成功后,就可以下载、解压、使用了:

                          看起来挺简单的,.. 下面来具体试试。


                          下载对应操作系统的软件包后,然后打开命令行窗口:

                            (base) adamhuan@Leviathan cpolar % pwd
                            /Users/adamhuan/cpolar
                            (base) adamhuan@Leviathan cpolar %
                            (base) adamhuan@Leviathan cpolar % ls -ltr
                            total 14144
                            -rw-r--r--@ 1 adamhuan staff 7241308 6 5 15:07 cpolar-stable-darwin-arm64.zip
                            (base) adamhuan@Leviathan cpolar %
                            (base) adamhuan@Leviathan cpolar % unzip cpolar-stable-darwin-arm64.zip
                            Archive: cpolar-stable-darwin-arm64.zip
                            inflating: cpolar
                            (base) adamhuan@Leviathan cpolar %
                            (base) adamhuan@Leviathan cpolar % ls -ltr
                            total 52752
                            -rwxr-xr-x@ 1 adamhuan staff 19764592 11 5 2022 cpolar
                            -rw-r--r--@ 1 adamhuan staff 7241308 6 5 15:07 cpolar-stable-darwin-arm64.zip
                            (base) adamhuan@Leviathan cpolar %
                            (base) adamhuan@Leviathan cpolar % ./cpolar help
                            Usage: ./cpolar <command> [OPTIONS] <local port or address>
                            Options:


                            Examples:
                            cpolar http 80 # secure public URL for port 80 website server
                            cpolar http -subdomain=baz 8080 # port 8080 available at baz.cpolar.io
                            cpolar http foo.dev:80 # tunnel to host:port instead of localhost
                            cpolar tcp 22 # tunnel arbitrary TCP traffic to port 22
                            cpolar http -hostname="example.com" -httpauth="user:password" 10.0.0.1:8080
                            cpolar ftp 21 # tunnel to port 21 for ftp server
                            cpolar tls 443 # create tls tunnel
                            cpolar service install # install cpolar daemon
                            cpolar service start # start cpolar daemon
                            cpolar service stop # stop cpolar daemon
                            cpolar service uninstall # uninstall cpolar daemon


                            Advanced usage: cpolar <command> [OPTIONS] [command args] [...]


                            Commands:
                            cpolar authtoken Save authtoken to configuration file
                            cpolar http Start an http tunnel
                            cpolar tcp Start a tcp tunnel
                            cpolar start [tunnel] [...] Start tunnels by name from config file
                            cpolar start-all Start all tunnels defined in config file
                            cpolar list List tunnel names from config file
                            cpolar help Print help
                            cpolar version Print cpolar version
                            cpolar ftp Start a ftp tunnel
                            cpolar tls Start a tls tunnel
                            cpolar service <install|start|stop|uninstall> Daemon Manager


                            Examples:
                            cpolar start www api blog pubsub
                            cpolar start ssh -log=stdout -config=cpolar.yml
                            cpolar start-all
                            cpolar version
                            cpolar service install
                            cpolar service start
                            cpolar service stop
                            cpolar service uninstall
                            cpolar http 8080


                            cpolar is a command line application, try typing 'cpolar http 8080'
                            at this terminal prompt to expose port 8080.


                            (base) adamhuan@Leviathan cpolar %


                            然后通过官方提供的Token连接到自己的账户:

                              (base) adamhuan@Leviathan cpolar % ./cpolar authtoken ZGQxNmRmM2UtMDQzNS00MGJlLTkxxxxxxxxxxxOWJmNmZk 


                              Authtoken saved to configuration file: /Users/adamhuan/.cpolar/cpolar.yml
                              (base) adamhuan@Leviathan cpolar %


                              进行服务的安装:

                                (base) adamhuan@Leviathan cpolar % sudo ./cpolar service install
                                Password:
                                (base) adamhuan@Leviathan cpolar %


                                启动:

                                  (base) adamhuan@Leviathan cpolar % sudo ./cpolar service start
                                  (base) adamhuan@Leviathan cpolar %


                                  浏览器:

                                  http://localhost:9200

                                  登录后:

                                  创建一个针对当前Django项目的隧道:

                                  高级中,设置「Host头域」:

                                  最后,点击「创建」,创建成功后,就可以在「隧道列表」中看到了:

                                  这时候去看「在线隧道列表」:

                                  你就可以看到它们对应的公网地址了。


                                  来试试用公网IP访问:


                                  内网IP:

                                  http://localhost:8000/api/card/


                                  公网IP:

                                  https://fc53c77.r9.cpolar.top/api/card/

                                  可以看到,「内网穿透」配置成功了。


                                  需要注意的是,由于我们使用的内网穿透是免费的版本,所以很可能会有有效期,有效期过了之后,公网地址将会失效;


                                  在配置支付宝的回调与通知地址之前,请确认「穿透」地址的有效性。


                                  09

                                  支付宝沙箱「通知地址」


                                  在「沙箱应用」中配置「授权回调地址」:

                                  https://open.alipay.com/develop/sandbox/app


                                  可以看到,在这里可以配置回调与通知的地址。

                                  但是,更确定的方法是在代码的请求中进行配置。


                                  10

                                  Django中配置「通知」


                                  配置路由:

                                  然后,在Django的全局配置「settings.py」中设置相关的变量(完整的回调与通知地址):

                                    # 回调地址  Return URL
                                    ALIPAY_RETURN_URL = "http://6baab108.r10.cpolar.top/orders/"


                                    # 通知地址 Notify URL
                                    ALIPAY_NOTIFY_URL = "http://6baab108.r10.cpolar.top/api/callback/"


                                    # 商家ID
                                    ALIPAY_SELLER_PID = '2088721036954856'


                                    由于新增了回调与通知地址的配置,因此在构造「支付宝请求」的时候,需要增加新的属性,以确保请求「一定拿到」交易成功或失败后的「通知地址」或「回调地址」


                                    更新后的支付宝的自定义工具类完整代码如下所示:

                                    「utils/payment_alipay.py」

                                      #!/usr/bin/env python
                                      # -*- coding: UTF-8 -*-


                                      # ——————————————————
                                      # 脚本说明:
                                      # 支付方法:
                                      # 使用「支付宝」的「沙箱环境」
                                      # ——————————————————


                                      # ========================================
                                      # 开始
                                      # ))))))))) 模块包导入
                                      import logging
                                      import traceback


                                      # 『单独运行』使用
                                      # 如果「settings」来自Django,需要社会环境变量
                                      # import os
                                      # import sys
                                      # sys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')
                                      # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')


                                      # 「结合Django」使用
                                      from django.conf import settings




                                      from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
                                      from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
                                      from alipay.aop.api.FileItem import FileItem
                                      from alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModel
                                      from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
                                      from alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModel
                                      from alipay.aop.api.domain.GoodsDetail import GoodsDetail
                                      from alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfo
                                      from alipay.aop.api.domain.SettleInfo import SettleInfo
                                      from alipay.aop.api.domain.SubMerchant import SubMerchant
                                      from alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequest
                                      from alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequest
                                      from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
                                      from alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequest
                                      from alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponse
                                      from alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse


                                      # ))))))))) 日志
                                      logging.basicConfig(
                                      level=logging.INFO,
                                      format='%(asctime)s %(levelname)s %(message)s',
                                      filemode='a',)
                                      logger = logging.getLogger('')


                                      # ))))))))) 类名


                                      # ))))))))) 执行阶段
                                      class Alipay():
                                      """
                                      设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。
                                      """
                                      alipay_client_config = AlipayClientConfig()


                                      # =================
                                      # Default
                                      # =================


                                      # 旧版本的API:openapi.alipay.com
                                      # 新版本的API:openapi-sandbox.dl.alipaydev.com
                                      # alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'


                                      # alipay_client_config.app_id = '[your app_id]'
                                      # alipay_client_config.app_private_key = '[your app private key]'
                                      # alipay_client_config.alipay_public_key = '[alipay public key]'


                                      # =================
                                      # For Django Custom
                                      # =================


                                      alipay_client_config.server_url = settings.ALIPAY_SERVER_URL
                                      alipay_client_config.app_id = settings.ALIPAY_APP_ID
                                      alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY
                                      alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY


                                      # alipay_client_config.notify_url = settings.ALIPAY_NOTIFY_URL
                                      # alipay_client_config.return_url = settings.ALIPAY_RETURN_URL


                                      # 输出
                                      # print("")
                                      # print("==========================================")
                                      # print(settings.ALIPAY_SERVER_URL)


                                      def __init__(self):
                                      """
                                      得到客户端对象。
                                      注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。
                                      logger参数用于打印日志,不传则不打印,建议传递。
                                      """
                                      self.client = DefaultAlipayClient(
                                      alipay_client_config=self.alipay_client_config,
                                      logger=logger
                                      )


                                      def trade_page(self,out_trade_no,total_amount,subject,body,product_code):
                                      """
                                      页面接口示例:alipay.trade.page.pay
                                      「选择需要的接口」
                                      """
                                      # 对照接口文档,构造请求对象
                                      model = AlipayTradePagePayModel()


                                      # 请求参数:必选
                                      # model.out_trade_no = "pay202406051355202"
                                      # model.total_amount = 50
                                      # model.subject = "测试"
                                      # model.body = "支付宝测试"
                                      # model.product_code = "FAST_INSTANT_TRADE_PAY"


                                      model.out_trade_no = out_trade_no
                                      model.total_amount = total_amount
                                      model.subject = subject
                                      model.body = body
                                      model.product_code = product_code


                                      # 请求参数:可选
                                      # settle_detail_info = SettleDetailInfo()
                                      # settle_detail_info.amount = 50
                                      # settle_detail_info.trans_in_type = "userId"
                                      # settle_detail_info.trans_in = "2088302300165604"
                                      # settle_detail_infos = list()
                                      # settle_detail_infos.append(settle_detail_info)
                                      # settle_info = SettleInfo()
                                      # settle_info.settle_detail_infos = settle_detail_infos
                                      # model.settle_info = settle_info
                                      # sub_merchant = SubMerchant()
                                      # sub_merchant.merchant_id = "2088301300153242"
                                      # model.sub_merchant = sub_merchant


                                      # 构造的请求
                                      request = AlipayTradePagePayRequest(biz_model=model)


                                      request.notify_url = settings.ALIPAY_NOTIFY_URL
                                      # request.return_url = settings.ALIPAY_RETURN_URL


                                      # request.notify_url = settings.ALIPAY_RETURN_URL
                                      # request.return_url = settings.ALIPAY_NOTIFY_URL


                                      # 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段
                                      response = self.client.page_execute(request, http_method="GET")


                                      # 输出
                                      # print("")
                                      # print("==========================================")
                                      # print("alipay.trade.page.pay response:" + response)


                                      # 返回阶段
                                      return response


                                      # ))))))))) 结束


                                      # ========================================
                                      # 结束



                                      最后,配置通知路由的对应视图:

                                        from django.shortcuts import render
                                        from django.utils import timezone
                                        from django_filters import rest_framework as filters
                                        from django.conf import settings


                                        from datetime import datetime


                                        from rest_framework import viewsets,status
                                        from rest_framework.views import APIView
                                        from rest_framework.response import Response
                                        from rest_framework import pagination


                                        from trade.models import *
                                        from trade.serializers import *
                                        from trade.permissions import *


                                        from account.models import *


                                        from utils.error import *
                                        from utils.common import *


                                        from utils.payment_alipay import *
                                        # from utils.payment_alipay_new import *


                                        # Create your views here.


                                        # Card


                                        class CardFilter(filters.FilterSet):
                                        card_name = filters.CharFilter(lookup_expr='icontains')
                                        duration = filters.NumberFilter()


                                        class Meta:
                                        model = Card
                                        fields = ['card_name', 'duration']


                                        class CardPagination(pagination.PageNumberPagination):
                                        page_size = 12
                                        max_page_size = 20
                                        page_size_query_param = 'page_size'


                                        class CardViewSet(viewsets.ModelViewSet):
                                        queryset = Card.objects.all()
                                        serializer_class = CardSerializer
                                        filter_backends = (filters.DjangoFilterBackend,)
                                        filterset_class = CardFilter
                                        pagination_class = CardPagination


                                        # permission_classes = [
                                        # IsAdminUserOrReadonly,
                                        # ]


                                        # Order


                                        class OrderFilter(filters.FilterSet):
                                        pay_status = filters.CharFilter(lookup_expr='icontains')
                                        pay_type = filters.CharFilter(lookup_expr='icontains')


                                        class Meta:
                                        model = Order
                                        fields = ['pay_status', 'pay_type']


                                        class OrderPagination(pagination.PageNumberPagination):
                                        page_size = 12
                                        max_page_size = 20
                                        page_size_query_param = 'page_size'


                                        class OrderViewset(viewsets.ModelViewSet):
                                        queryset = Order.objects.all()
                                        serializer_class = OrderSerializer
                                        filter_backends = (filters.DjangoFilterBackend,)
                                        filterset_class = OrderFilter
                                        pagination_class = OrderPagination


                                        permission_classes = [
                                        IsAdminUserOrReadonly,
                                        ]


                                        # Alipay


                                        class AlipayAPIView(APIView):


                                        # 发起支付宝支付「场景」:
                                        # 1. 创建新的支付
                                        # 2. 支付未完成的订单
                                        def get(self, request):


                                        # 判断卡号「card_id」是否正确
                                        card_id = request.GET.get("card_id", None)


                                        print("================")
                                        print(card_id)


                                        try:
                                        card = Card.objects.get(id=card_id)
                                        except:
                                        return Response(
                                        response_data(
                                        *TradeError.CardParamError
                                        )
                                        )


                                        # 订单号
                                        out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)


                                        # 创建订单
                                        try:
                                        Order.objects.create(
                                        user = Profile.objects.get(user=request.user),
                                        card = card,
                                        order_sn = out_trade_no,
                                        order_mount = card.card_price,
                                        pay_time = timezone.now(),
                                        )
                                        except:
                                        return Response(
                                        response_data(
                                        *TradeError.OrderCreateError
                                        )
                                        )


                                        # 请求支付宝
                                        try:
                                        # pip install alipay-sdk-python --upgrade
                                        alipay = Alipay()
                                        pay_url = alipay.trade_page(
                                        out_trade_no=out_trade_no,
                                        total_amount=str(card.card_price),
                                        subject=card.card_name,
                                        body="「支付宝 - 沙箱环境」支付",
                                        product_code="FAST_INSTANT_TRADE_PAY"
                                        )


                                        # pip install python-alipay-sdk
                                        # 电脑网站支付 alipay.trade.page.pay
                                        # pay_url = alipay.api_alipay_trade_page_pay(
                                        # out_trade_no=out_trade_no,
                                        # total_amount=str(card.card_price),
                                        # subject=card.card_name,
                                        # return_url=settings.ALIPAY_RETURN_URL,
                                        # notify_url=settings.ALIPAY_NOTIFY_URL, # 可选,不填则使用默认 notify url
                                        # )


                                        # 返回
                                        return Response(pay_url)
                                        except:
                                        return Response(
                                        response_data(
                                        *TradeError.AlipayRequestError
                                        )
                                        )


                                        class AlipayCallbackAPIView(APIView):


                                        def get(self, request):
                                        return Response("阿里支付 - 回调函数")


                                        def post(self, request):
                                        params = request.POST.dict()
                                        print(params)


                                        # 返回阶段
                                        return Response("success")



                                        ——————————————————

                                        当你完成了上面的配置后,当你再次完成支付后,你的PyCharm的命令行就可以拿到支付宝反馈回来的「通知信息」:

                                          {'gmt_create': '2024-06-06 00:30:07', 'charset': 'utf-8', 'gmt_payment': '2024-06-06 00:30:14', 'notify_time': '2024-06-06 00:30:15', 'subject': '黄金VIP', 'sign': '5yy89qKtqnhJVi8vuiRoxC5iUUJ0aNt3hX1l+0sDBe/vHCG6clZtxJqt7MysPwT7rjgo4gX7O7AQ48HuXjSzCsisjagkzdRafYZlB7X1Ph7P0sjetL9jyI8aNge4myBKmcpvflny6lnJZSEyM2fCVxPnFqWQ4YUyOgxJZStUr3wJZAOnJ3Dt4aXSRJAuuC0FQ+KW51L6nCTfwGrl9Z3IIndLtOrUP9xZa/92kKtJZGt1eh2V/YKZc2+DHD9C3Qj81LoYgEz6Zrt+gHZrsFRPv01ZgiIFY2+tPBDmyTutV2IvXSg0GL47BbS1/wHbzVrsM8MvgX9SPfqqjZp8V0YrVQ==', 'buyer_id': '2088722036954866', 'body': '「支付宝 - 沙箱环境」支付', 'invoice_amount': '148.00', 'version': '1.0', 'notify_id': '2024060601222003015154860503098223', 'fund_bill_list': '[{"amount":"148.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': 'pay202406060029514456', 'total_amount': '148.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2024060622001454860502950171', 'auth_app_id': '9021000137680887', 'receipt_amount': '148.00', 'point_amount': '0.00', 'buyer_pay_amount': '148.00', 'app_id': '9021000137680887', 'sign_type': 'RSA2', 'seller_id': '2088721036954856'}
                                          [06/Jun/2024 00:30:15] "POST api/callback/ HTTP/1.1" 200 9



                                          11

                                          支付宝「异步通知验签」


                                          在上面的步骤中,当我们在支付宝的交易完成后,会通过「NOTIFY_URL」获得来自支付宝反馈的通知信息。


                                          拿到了这个信息后,就可以进行「异步通知的验签」操作。


                                          关于这一部分,官方的文档地址为:

                                          https://opendocs.alipay.com/open/270/105902?pathHash=d5cd617e


                                          在官方文档中,对异步返回结果的验签,一共有五步:


                                          这一部分修改了自定义的支付宝的工具类,最新的完整代码如下所示:

                                          「utils/payment_alipay.py」

                                            #!/usr/bin/env python
                                            # -*- coding: UTF-8 -*-


                                            # ——————————————————
                                            # 脚本说明:
                                            # 支付方法:
                                            # 使用「支付宝」的「沙箱环境」
                                            # ——————————————————


                                            # ========================================
                                            # 开始
                                            # ))))))))) 模块包导入
                                            import base64
                                            import logging
                                            import traceback


                                            # 『单独运行』使用
                                            # 如果「settings」来自Django,需要社会环境变量
                                            # import os
                                            # import sys
                                            # sys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')
                                            # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')


                                            # 「结合Django」使用
                                            import rsa
                                            from django.conf import settings




                                            from alipay.aop.api.AlipayClientConfig import AlipayClientConfig


                                            from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient


                                            from alipay.aop.api.FileItem import FileItem


                                            from alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModel
                                            from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
                                            from alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModel
                                            from alipay.aop.api.domain.GoodsDetail import GoodsDetail
                                            from alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfo
                                            from alipay.aop.api.domain.SettleInfo import SettleInfo
                                            from alipay.aop.api.domain.SubMerchant import SubMerchant


                                            from alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequest
                                            from alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequest
                                            from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
                                            from alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequest


                                            from alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponse
                                            from alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse


                                            # 通过 RSA 进行验签
                                            from alipay.aop.api.util.SignatureUtils import verify_with_rsa


                                            # ))))))))) 日志
                                            logging.basicConfig(
                                            level=logging.INFO,
                                            format='%(asctime)s %(levelname)s %(message)s',
                                            filemode='a',)
                                            logger = logging.getLogger('')


                                            # ))))))))) 类名


                                            # ))))))))) 执行阶段
                                            class Alipay():
                                            """
                                            设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。
                                            """
                                            alipay_client_config = AlipayClientConfig()


                                            # =================
                                            # Default
                                            # =================


                                            # 旧版本的API:openapi.alipay.com
                                            # 新版本的API:openapi-sandbox.dl.alipaydev.com
                                            # alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'


                                            # alipay_client_config.app_id = '[your app_id]'
                                            # alipay_client_config.app_private_key = '[your app private key]'
                                            # alipay_client_config.alipay_public_key = '[alipay public key]'


                                            # =================
                                            # For Django Custom
                                            # =================


                                            alipay_client_config.server_url = settings.ALIPAY_SERVER_URL
                                            alipay_client_config.app_id = settings.ALIPAY_APP_ID
                                            alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY
                                            alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY


                                            # alipay_client_config.notify_url = settings.ALIPAY_NOTIFY_URL
                                            # alipay_client_config.return_url = settings.ALIPAY_RETURN_URL


                                            # 输出
                                            # print("")
                                            # print("==========================================")
                                            # print(settings.ALIPAY_SERVER_URL)


                                            def __init__(self):
                                            """
                                            得到客户端对象。
                                            注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。
                                            logger参数用于打印日志,不传则不打印,建议传递。
                                            """
                                            self.client = DefaultAlipayClient(
                                            alipay_client_config=self.alipay_client_config,
                                            logger=logger
                                            )


                                            def trade_page(self,out_trade_no,total_amount,subject,body,product_code):
                                            """
                                            页面接口示例:alipay.trade.page.pay
                                            「选择需要的接口」
                                            """
                                            # 对照接口文档,构造请求对象
                                            model = AlipayTradePagePayModel()


                                            # 请求参数:必选
                                            # model.out_trade_no = "pay202406051355202"
                                            # model.total_amount = 50
                                            # model.subject = "测试"
                                            # model.body = "支付宝测试"
                                            # model.product_code = "FAST_INSTANT_TRADE_PAY"


                                            model.out_trade_no = out_trade_no
                                            model.total_amount = total_amount
                                            model.subject = subject
                                            model.body = body
                                            model.product_code = product_code


                                            # 请求参数:可选
                                            # settle_detail_info = SettleDetailInfo()
                                            # settle_detail_info.amount = 50
                                            # settle_detail_info.trans_in_type = "userId"
                                            # settle_detail_info.trans_in = "2088302300165604"
                                            # settle_detail_infos = list()
                                            # settle_detail_infos.append(settle_detail_info)
                                            # settle_info = SettleInfo()
                                            # settle_info.settle_detail_infos = settle_detail_infos
                                            # model.settle_info = settle_info
                                            # sub_merchant = SubMerchant()
                                            # sub_merchant.merchant_id = "2088301300153242"
                                            # model.sub_merchant = sub_merchant


                                            # 构造的请求
                                            request = AlipayTradePagePayRequest(biz_model=model)


                                            request.notify_url = settings.ALIPAY_NOTIFY_URL
                                            # request.return_url = settings.ALIPAY_RETURN_URL


                                            # request.notify_url = settings.ALIPAY_RETURN_URL
                                            # request.return_url = settings.ALIPAY_NOTIFY_URL


                                            # 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段
                                            response = self.client.page_execute(request, http_method="GET")


                                            # 输出
                                            # print("")
                                            # print("==========================================")
                                            # print("alipay.trade.page.pay response:" + response)


                                            # 返回阶段
                                            return response


                                            def verify_sign(self, unsigned_string, sign):
                                            """
                                            :param unsigned_string: 需要验签的内容
                                            :param sign: sign参数
                                            :return: True - 验签成功 False - 验签失败
                                            """
                                            return verify_with_rsa(
                                            settings.ALIPAY_PUBLIC_KEY,
                                            bytes(unsigned_string, encoding='utf-8'),
                                            sign
                                            )


                                            # ))))))))) 结束


                                            # ========================================
                                            # 结束



                                            视图「trade/views.py」

                                              from django.shortcuts import render
                                              from django.utils import timezone
                                              from django_filters import rest_framework as filters
                                              from django.conf import settings


                                              from datetime import datetime


                                              from rest_framework import viewsets,status
                                              from rest_framework.views import APIView
                                              from rest_framework.response import Response
                                              from rest_framework import pagination


                                              from trade.models import *
                                              from trade.serializers import *
                                              from trade.permissions import *


                                              from account.models import *


                                              from utils.error import *
                                              from utils.common import *


                                              from utils.payment_alipay import *
                                              # from utils.payment_alipay_new import *


                                              # Create your views here.


                                              # Card


                                              class CardFilter(filters.FilterSet):
                                              card_name = filters.CharFilter(lookup_expr='icontains')
                                              duration = filters.NumberFilter()


                                              class Meta:
                                              model = Card
                                              fields = ['card_name', 'duration']


                                              class CardPagination(pagination.PageNumberPagination):
                                              page_size = 12
                                              max_page_size = 20
                                              page_size_query_param = 'page_size'


                                              class CardViewSet(viewsets.ModelViewSet):
                                              queryset = Card.objects.all()
                                              serializer_class = CardSerializer
                                              filter_backends = (filters.DjangoFilterBackend,)
                                              filterset_class = CardFilter
                                              pagination_class = CardPagination


                                              # permission_classes = [
                                              # IsAdminUserOrReadonly,
                                              # ]


                                              # Order


                                              class OrderFilter(filters.FilterSet):
                                              pay_status = filters.CharFilter(lookup_expr='icontains')
                                              pay_type = filters.CharFilter(lookup_expr='icontains')


                                              class Meta:
                                              model = Order
                                              fields = ['pay_status', 'pay_type']


                                              class OrderPagination(pagination.PageNumberPagination):
                                              page_size = 12
                                              max_page_size = 20
                                              page_size_query_param = 'page_size'


                                              class OrderViewset(viewsets.ModelViewSet):
                                              queryset = Order.objects.all()
                                              serializer_class = OrderSerializer
                                              filter_backends = (filters.DjangoFilterBackend,)
                                              filterset_class = OrderFilter
                                              pagination_class = OrderPagination


                                              permission_classes = [
                                              IsAdminUserOrReadonly,
                                              ]


                                              # Alipay


                                              class AlipayAPIView(APIView):


                                              # 发起支付宝支付「场景」:
                                              # 1. 创建新的支付
                                              # 2. 支付未完成的订单
                                              def get(self, request):


                                              # 判断卡号「card_id」是否正确
                                              card_id = request.GET.get("card_id", None)


                                              print("================")
                                              print(card_id)


                                              try:
                                              card = Card.objects.get(id=card_id)
                                              except:
                                              return Response(
                                              response_data(
                                              *TradeError.CardParamError
                                              )
                                              )


                                              # 订单号
                                              out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)


                                              # 创建订单
                                              try:
                                              Order.objects.create(
                                              user = Profile.objects.get(user=request.user),
                                              card = card,
                                              order_sn = out_trade_no,
                                              order_mount = card.card_price,
                                              pay_time = timezone.now(),
                                              )
                                              except:
                                              return Response(
                                              response_data(
                                              *TradeError.OrderCreateError
                                              )
                                              )


                                              # 请求支付宝
                                              try:
                                              # pip install alipay-sdk-python --upgrade
                                              alipay = Alipay()
                                              pay_url = alipay.trade_page(
                                              out_trade_no=out_trade_no,
                                              total_amount=str(card.card_price),
                                              subject=card.card_name,
                                              body="「支付宝 - 沙箱环境」支付",
                                              product_code="FAST_INSTANT_TRADE_PAY"
                                              )


                                              # pip install python-alipay-sdk
                                              # 电脑网站支付 alipay.trade.page.pay
                                              # pay_url = alipay.api_alipay_trade_page_pay(
                                              # out_trade_no=out_trade_no,
                                              # total_amount=str(card.card_price),
                                              # subject=card.card_name,
                                              # return_url=settings.ALIPAY_RETURN_URL,
                                              # notify_url=settings.ALIPAY_NOTIFY_URL, # 可选,不填则使用默认 notify url
                                              # )


                                              # 返回
                                              return Response(pay_url)
                                              except:
                                              return Response(
                                              response_data(
                                              *TradeError.AlipayRequestError
                                              )
                                              )


                                              class AlipayCallbackAPIView(APIView):


                                              def get(self, request):
                                              return Response("阿里支付 - 回调函数")


                                              def post(self, request):


                                              # =======================
                                              # 拿到异步返回的结果
                                              params = request.POST.dict()
                                              print(params)


                                              # =======================
                                              # 对异步返回结果的验签


                                              # ----------------
                                              # Step 1 - 在通知返回参数列表中,除去 sign、sign_type 两个参数外,凡是通知返回回来的参数皆是待验签的参数。
                                              # --- SIGN
                                              # del params['sign']
                                              sign = params.pop('sign')


                                              # --- SIGN_TYPE
                                              del params['sign_type']


                                              # ----------------
                                              # Step 2 - 将剩下参数进行 url_decode,然后进行字典排序,组成字符串,得到待签名字符串


                                              # 对字典进行排序
                                              # 1. 列表生成式
                                              sorted_list = sorted([(k,v) for k,v in params.items() ])


                                              # 拼接字符串
                                              unsigned_string = '&'.join(f"{k}={v}" for k,v in sorted_list)


                                              # ----------------
                                              # Step 3 - 将签名参数(sign)使用 base64 解码为字节码串
                                              # Step 4 - 使用 RSA 的验签方法,通过签名字符串、签名参数(经过 base64 解码)及支付宝公钥验证签名


                                              # ALIPAY 的官方 PIP 包有提供方法解决「Step 3 Step 4」
                                              # from alipay.aop.api.util.SignatureUtils import verify_with_rsa
                                              # 该方法在单独实现的 ALIPAY 的工具类中有实现


                                              alipay = Alipay()
                                              if not alipay.verify_sign(
                                              unsigned_string=unsigned_string,
                                              sign=sign
                                              ):
                                              print("==================")
                                              print("验签失败")


                                              return Response(
                                              '验签失败'
                                              )


                                              # ----------------
                                              # Step 5 - 需要严格按照如下描述校验通知数据的正确性
                                              # 1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
                                              # 2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。
                                              # 3. 校验通知中的 seller_id(或者 seller_email)是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个 seller_id/seller_email)。
                                              # 4. 验证 app_id 是否为该商家本身。
                                              # ----------------
                                              # 上述 1、2、3、4 有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商家必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
                                              # ----------------


                                              try:
                                              order = Order.objects.get(order_sn=params.get('out_trade_no'))
                                              except:
                                              return Response('「订单」不存在')


                                              if params.get('total_amount') != str(order.order_mount):
                                              print("==================")
                                              print("验签失败")


                                              return Response(
                                              '验签失败'
                                              )


                                              if params.get('seller_id') != settings.ALIPAY_SELLER_PID:
                                              print("==================")
                                              print("验签失败")


                                              return Response(
                                              '验签失败'
                                              )


                                              if params.get('app_id') != settings.ALIPAY_APP_ID:
                                              print("==================")
                                              print("验签失败")


                                              return Response(
                                              '验签失败'
                                              )


                                              if params.get('trade_status') not in ['TRADE_SUCCESS','TRADE_FINISHED']:
                                              print("==================")
                                              print("验签失败")


                                              return Response(
                                              '验签失败'
                                              )


                                              # 至此,验签「全部通过」
                                              # 执行其他的业务逻辑


                                              print("==================")
                                              print("验签成功")


                                              return Response("success")



                                              ——————————————————

                                              当完成了上述配置之后,再次完成支付宝的支付后,就可以对支付宝返回的结果进行验签了:

                                                {'gmt_create': '2024-06-06 01:47:09', 'charset': 'utf-8', 'gmt_payment': '2024-06-06 01:47:25', 'notify_time': '2024-06-06 01:47:27', 'subject': '黄金VIP', 'sign': 'kglWIvbDUeGINU5y4cZnE/1zOQ5JMMexVFrzISlcfuhfQqPnyu3FNgZzRo7SABlwBcH6mXm1lOFKWnl7e+lJIv8MIZa2TEG1a9nIFiMmJp2fXOB3W+KjQYMUcAxnktgkzskeriAhc057GJNqE7c8hNU718YfcrmMcveOqYcncJy0U9aKT/08s/8JRxKwywG/GnOwnl6/Na+t1ZBM3b3zmNVRz4mwXgtXah++jJOQvKrCNOefh6l5fnt3w15TOzBcl5rH5420KP3+WcQl0CTCMSuV/d7V7ftkzrs0Vj2XJS3zh/ggj5x+Qz0jJxV4jvYywYAE/cyY6SN1pK994HVqyA==', 'buyer_id': '2088722036954866', 'body': '「支付宝 - 沙箱环境」支付', 'invoice_amount': '148.00', 'version': '1.0', 'notify_id': '2024060601222014726054860503095122', 'fund_bill_list': '[{"amount":"148.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': 'pay202406060147015230', 'total_amount': '148.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2024060622001454860502959606', 'auth_app_id': '9021000137680887', 'receipt_amount': '148.00', 'point_amount': '0.00', 'buyer_pay_amount': '148.00', 'app_id': '9021000137680887', 'sign_type': 'RSA2', 'seller_id': '2088721036954856'}
                                                ==================
                                                验签成功
                                                [06/Jun/2024 01:47:27] "POST api/callback/ HTTP/1.1" 200 9


                                                至此,当支付宝的交易完成后的验签操作就全部完成了。

                                                接下来,就是支付交易完成后的业务逻辑的处理了。


                                                12

                                                支付后「业务逻辑」


                                                在支付宝支付成功后,业务逻辑的处理也是在一样的视图中进行处理的。


                                                添加了业务逻辑后,完整的视图代码如下所示:


                                                文件「trade/views.py」:

                                                  from django.shortcuts import render
                                                  from django.utils import timezone
                                                  from django_filters import rest_framework as filters
                                                  from django.conf import settings
                                                  from django.db import transaction


                                                  from datetime import datetime, timedelta


                                                  from rest_framework import viewsets,status
                                                  from rest_framework.views import APIView
                                                  from rest_framework.response import Response
                                                  from rest_framework import pagination


                                                  from trade.models import *
                                                  from trade.serializers import *
                                                  from trade.permissions import *


                                                  from account.models import *


                                                  from utils.error import *
                                                  from utils.common import *


                                                  from utils.payment_alipay import *
                                                  # from utils.payment_alipay_new import *


                                                  # Create your views here.


                                                  # Card


                                                  class CardFilter(filters.FilterSet):
                                                  card_name = filters.CharFilter(lookup_expr='icontains')
                                                  duration = filters.NumberFilter()


                                                  class Meta:
                                                  model = Card
                                                  fields = ['card_name', 'duration']


                                                  class CardPagination(pagination.PageNumberPagination):
                                                  page_size = 12
                                                  max_page_size = 20
                                                  page_size_query_param = 'page_size'


                                                  class CardViewSet(viewsets.ModelViewSet):
                                                  queryset = Card.objects.all()
                                                  serializer_class = CardSerializer
                                                  filter_backends = (filters.DjangoFilterBackend,)
                                                  filterset_class = CardFilter
                                                  pagination_class = CardPagination


                                                  # permission_classes = [
                                                  # IsAdminUserOrReadonly,
                                                  # ]


                                                  # Order


                                                  class OrderFilter(filters.FilterSet):
                                                  pay_status = filters.CharFilter(lookup_expr='icontains')
                                                  pay_type = filters.CharFilter(lookup_expr='icontains')


                                                  class Meta:
                                                  model = Order
                                                  fields = ['pay_status', 'pay_type']


                                                  class OrderPagination(pagination.PageNumberPagination):
                                                  page_size = 12
                                                  max_page_size = 20
                                                  page_size_query_param = 'page_size'


                                                  class OrderViewset(viewsets.ModelViewSet):
                                                  queryset = Order.objects.all()
                                                  serializer_class = OrderSerializer
                                                  filter_backends = (filters.DjangoFilterBackend,)
                                                  filterset_class = OrderFilter
                                                  pagination_class = OrderPagination


                                                  permission_classes = [
                                                  IsAdminUserOrReadonly,
                                                  ]


                                                  # Alipay


                                                  class AlipayAPIView(APIView):


                                                  # 发起支付宝支付「场景」:
                                                  # 1. 创建新的支付
                                                  # 2. 支付未完成的订单
                                                  def get(self, request):


                                                  # 判断卡号「card_id」是否正确
                                                  card_id = request.GET.get("card_id", None)


                                                  print("================")
                                                  print(card_id)


                                                  try:
                                                  card = Card.objects.get(id=card_id)
                                                  except:
                                                  return Response(
                                                  response_data(
                                                  *TradeError.CardParamError
                                                  )
                                                  )


                                                  # 订单号
                                                  out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)


                                                  # 创建订单
                                                  try:
                                                  Order.objects.create(
                                                  user = Profile.objects.get(user=request.user),
                                                  card = card,
                                                  order_sn = out_trade_no,
                                                  order_mount = card.card_price,
                                                  pay_time = timezone.now(),
                                                  )
                                                  except:
                                                  return Response(
                                                  response_data(
                                                  *TradeError.OrderCreateError
                                                  )
                                                  )


                                                  # 请求支付宝
                                                  try:
                                                  # pip install alipay-sdk-python --upgrade
                                                  alipay = Alipay()
                                                  pay_url = alipay.trade_page(
                                                  out_trade_no=out_trade_no,
                                                  total_amount=str(card.card_price),
                                                  subject=card.card_name,
                                                  body="「支付宝 - 沙箱环境」支付",
                                                  product_code="FAST_INSTANT_TRADE_PAY"
                                                  )


                                                  # pip install python-alipay-sdk
                                                  # 电脑网站支付 alipay.trade.page.pay
                                                  # pay_url = alipay.api_alipay_trade_page_pay(
                                                  # out_trade_no=out_trade_no,
                                                  # total_amount=str(card.card_price),
                                                  # subject=card.card_name,
                                                  # return_url=settings.ALIPAY_RETURN_URL,
                                                  # notify_url=settings.ALIPAY_NOTIFY_URL, # 可选,不填则使用默认 notify url
                                                  # )


                                                  # 返回
                                                  return Response(pay_url)
                                                  except:
                                                  return Response(
                                                  response_data(
                                                  *TradeError.AlipayRequestError
                                                  )
                                                  )


                                                  class AlipayCallbackAPIView(APIView):


                                                  def get(self, request):
                                                  return Response("阿里支付 - 回调函数")


                                                  def post(self, request):


                                                  # =======================
                                                  # 拿到异步返回的结果
                                                  params = request.POST.dict()
                                                  print(params)


                                                  # =======================
                                                  # 对异步返回结果的验签


                                                  # ----------------
                                                  # Step 1 - 在通知返回参数列表中,除去 sign、sign_type 两个参数外,凡是通知返回回来的参数皆是待验签的参数。
                                                  # --- SIGN
                                                  # del params['sign']
                                                  sign = params.pop('sign')


                                                  # --- SIGN_TYPE
                                                  del params['sign_type']


                                                  # ----------------
                                                  # Step 2 - 将剩下参数进行 url_decode,然后进行字典排序,组成字符串,得到待签名字符串


                                                  # 对字典进行排序
                                                  # 1. 列表生成式
                                                  sorted_list = sorted([(k,v) for k,v in params.items() ])


                                                  # 拼接字符串
                                                  unsigned_string = '&'.join(f"{k}={v}" for k,v in sorted_list)


                                                  # ----------------
                                                  # Step 3 - 将签名参数(sign)使用 base64 解码为字节码串
                                                  # Step 4 - 使用 RSA 的验签方法,通过签名字符串、签名参数(经过 base64 解码)及支付宝公钥验证签名


                                                  # ALIPAY 的官方 PIP 包有提供方法解决「Step 3 Step 4」
                                                  # from alipay.aop.api.util.SignatureUtils import verify_with_rsa
                                                  # 该方法在单独实现的 ALIPAY 的工具类中有实现


                                                  alipay = Alipay()
                                                  if not alipay.verify_sign(
                                                  unsigned_string=unsigned_string,
                                                  sign=sign
                                                  ):
                                                  print("==================")
                                                  print("验签失败")


                                                  return Response(
                                                  '验签失败'
                                                  )


                                                  # ----------------
                                                  # Step 5 - 需要严格按照如下描述校验通知数据的正确性
                                                  # 1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
                                                  # 2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。
                                                  # 3. 校验通知中的 seller_id(或者 seller_email)是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个 seller_id/seller_email)。
                                                  # 4. 验证 app_id 是否为该商家本身。
                                                  # ----------------
                                                  # 上述 1、2、3、4 有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商家必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
                                                  # ----------------


                                                  try:
                                                  order = Order.objects.get(order_sn=params.get('out_trade_no'))
                                                  except:
                                                  return Response('「订单」不存在')


                                                  if params.get('total_amount') != str(order.order_mount):
                                                  print("==================")
                                                  print("验签失败")


                                                  return Response(
                                                  '验签失败'
                                                  )


                                                  if params.get('seller_id') != settings.ALIPAY_SELLER_PID:
                                                  print("==================")
                                                  print("验签失败")


                                                  return Response(
                                                  '验签失败'
                                                  )


                                                  if params.get('app_id') != settings.ALIPAY_APP_ID:
                                                  print("==================")
                                                  print("验签失败")


                                                  return Response(
                                                  '验签失败'
                                                  )


                                                  if params.get('trade_status') not in ['TRADE_SUCCESS','TRADE_FINISHED']:
                                                  print("==================")
                                                  print("验签失败")


                                                  return Response(
                                                  '验签失败'
                                                  )


                                                  # 至此,验签「全部通过」


                                                  # 执行其他的业务逻辑


                                                  # 数据库「事务一致性」
                                                  with transaction.atomic():


                                                  # ======================
                                                  # --- 修改 ORDER 表
                                                  # ======================


                                                  order.trade_no = params.get('trade_no')
                                                  order.pay_status = params.get('trade_status')


                                                  # order.pay_time = datetime.now()
                                                  order.pay_time = timezone.now()


                                                  # 保存修改
                                                  order.save()


                                                  # ======================
                                                  # --- 修改 PROFILE 表
                                                  # ======================


                                                  profile = Profile.objects.get(uid=order.user.uid)
                                                  profile.is_upgrade = 1


                                                  # profile.upgrade_time = datetime.now()
                                                  profile.upgrade_time = timezone.now()


                                                  profile.upgrade_count += 1


                                                  # 如果首次充值或者会员已过期
                                                  # 如果会员未过期 - 在原来的基础时间上再次加上时长
                                                  if not profile.expire_time or profile.expire_time < timezone.now():
                                                  # profile.expire_time = datetime.now() + timedelta(days=order.card.duration)
                                                  profile.expire_time = timezone.now() + timedelta(days=order.card.duration)
                                                  else:
                                                  profile.expire_time = profile.expire_time + timedelta(days=order.card.duration)


                                                  # 保存修改
                                                  profile.save()


                                                  print("==================")
                                                  print("验签成功")


                                                  return Response("success")



                                                  13

                                                  终了


                                                  至此,关于「支付宝」的「沙箱环境」的「Python + Vue.JS」相关的完整使用,基本上就介绍完了。


                                                  如果你对本文所关联的项目感兴趣,它的「前端与后端」完整代码在我的GITEE仓库:


                                                  后端Django:

                                                  https://gitee.com/leviathan-litan/django_daily_media


                                                  前端Vue.JS:

                                                  https://gitee.com/leviathan-litan/django_daily_media_frontend






                                                  END




                                                  温馨提示



                                                  如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我。


                                                  文章转载自Nephilim,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                  评论