【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑

专题:Vue+Django REST framework前后端分离生鲜电商

Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。
Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ;
Django版本:2.2、djangorestframework:3.9.2。
前端Vue模板可以直接联系我拿。

更多内容请点击 我的博客 查看,欢迎来访。

支付接口类alipay.py解读

下载 alipay.py 源码

访问 https://github.com/liyaopinner/mxshop_sources 下载 “慕学生鲜电商的部分资源文件- alipay.py ”

复制内容放在 utils/alipay.py 文件中,对其进行修改,改为自己项目所需的

# -*- coding: utf-8 -*-

# pip install pycryptodome
__author__ = 'bobby'

from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64encode, b64decode
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from urllib.request import urlopen
from base64 import decodebytes, encodebytes
import json


class AliPay(object):
    """
    支付宝支付接口
    """

    def __init__(self, app_id, notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=True):
        self.app_id = app_id  # 支付宝分配的应用ID
        self.notify_url = notify_url  # 支付宝服务器主动通知商户服务器里指定的页面http/https路径;用户一旦支付,会向该url发一个异步的请求给自己服务器,这个一定需要公网可访问
        self.app_private_key_path = app_private_key_path  # 个人私钥路径
        self.app_private_key = None  # 个人私钥内容
        self.return_url = return_url  # 网页上支付完成后跳转回自己服务器的url
        with open(self.app_private_key_path) as fp:
            # 读取个人私钥文件提取到私钥内容
            self.app_private_key = RSA.importKey(fp.read())

        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            # 读取支付宝公钥文件提取公钥内容,支付宝公钥在代码中验签使用
            self.alipay_public_key = RSA.import_key(fp.read())

        if debug is True:
            # 使用沙箱的网关
            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        biz_content = {  # 请求参数的集合
            "subject": subject,  # 订单标题
            "out_trade_no": out_trade_no,  # 商户订单号,
            "total_amount": total_amount,  # 订单总金额
            "product_code": "FAST_INSTANT_TRADE_PAY",  # 销售产品码,默认
            # "qr_pay_mode":4
        }

        biz_content.update(kwargs)  # 合并其他请求参数字典
        data = self.build_body("alipay.trade.page.pay", biz_content, return_url)  # 将请求参数合并到公共参数字典的键biz_content中
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        """
        组合所有的请求参数到一个字典中
        :param method:
        :param biz_content:
        :param return_url:
        :return:
        """
        data = {
            "app_id": self.app_id,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is None:
            data["notify_url"] = self.notify_url
            data["return_url"] = self.return_url

        return data

    def ordered_data(self, data):
        """
        并按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。
        :param data:
        :return: 返回的是数组列表,按照数据中的k进行排序的
        """
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=(',', ':'))

        return sorted([(k, v) for k, v in data.items()])

    def sign(self, unsigned_string):
        """
        使用各自语言对应的SHA256WithRSA(对应sign_type为RSA2)或SHA1WithRSA(对应sign_type为RSA)签名函数利用商户私钥对待签名字符串进行签名,并进行Base64编码。
        :param unsigned_string:
        :return:
        """
        # 开始计算签名
        key = self.app_private_key
        signer = PKCS1_v1_5.new(key)
        signature = signer.sign(SHA256.new(unsigned_string))
        # base64 编码,转换为unicode表示并移除回车
        sign = encodebytes(signature).decode("utf8").replace("\n", "")
        return sign

    def sign_data(self, data):
        """
        获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数。
        进行排序。
        将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
        然后对该字符串进行签名。
        把生成的签名赋值给sign参数,拼接到请求参数中。
        :param data:
        :return:
        """
        data.pop("sign", None)
        # 排序后的字符串
        ordered_items = self.ordered_data(data)  # 数组列表,进行遍历拼接
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in ordered_items)  # 使用参数=值得格式用&连接

        sign = self.sign(unsigned_string.encode("utf-8"))  # 得到签名后的字符串
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in ordered_items)  # quote_plus给url进行预处理,特殊字符串在url中会有问题

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        return signed_string

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)

因为要用到加密,要用到Python中的 pip install pycryptodome,当前安装的版本为 3.8.2

项目配置服务器IP

在项目下创建 ProjectConfig.ini 配置文件,该配置文件主要是用来记录项目中涉密的配置,比如连接数据库帐密,服务器信息等。结构如下

[DjangoOnlineFreshSupermarket]
server_ip=xx.ip.ip.xx

接下来在 utils/alipay.py 中添加一个函数,用于获取这个服务器IP

def get_server_ip():
    """
    在项目根目录项创建ProjectConfig.ini配置文件,读取其中配置的IP地址
    :return:
    """
    import configparser
    import os
    import sys

    # 获取当前文件的路径(运行脚本)
    pwd = os.path.dirname(os.path.realpath(__file__))

    # 获取项目的根目录
    sys.path.append(pwd + "../")

    # 要想单独使用django的model,必须指定一个环境变量,会去settings配置找
    # 参照manage.py里面就知道为什么这样设置了
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoOnlineFreshSupermarket.settings')

    import django
    django.setup()
    from django.conf import settings

    config = configparser.ConfigParser()
    config.read(os.path.join(settings.BASE_DIR, 'ProjectConfig.ini'))
    server_ip = config['DjangoOnlineFreshSupermarket']['server_ip']
    return server_ip

可以在 utils/alipay.py 中添加一个 mian函数,测试下能够正确获取

if __name__ == "__main__":
    print(get_server_ip())
    server_ip = get_server_ip()  # 得到自己服务器的IP地址,也就是之前同步项目上去的

测试订单创建支付

在 main 函数中添加订单支付测试

if __name__ == "__main__":
    print(get_server_ip())
    server_ip = get_server_ip()  # 得到自己服务器的IP地址,也就是之前同步项目上去的
    alipay = AliPay(
        app_id="2016100900646609",  # 自己支付宝沙箱 APP ID
        notify_url="http://{}:8000/".format(server_ip),
        app_private_key_path="../apps/trade/keys/private_key_2048.txt",  # 可以使用相对路径那个
        alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        debug=True,  # 默认False,
        return_url="http://{}:8000/".format(server_ip)
    )

    # 创建订单
    url = alipay.direct_pay(
        subject="测试订单",
        out_trade_no="2019080716060001",
        total_amount=0.01
    )
    re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    print(re_url)

直接运行即可

这时候会得到一个链接

https://openapi.alipaydev.com/gateway.do?app_id=2016100900646609&biz_content=%7B%22subject%22%3A%22%5Cu6d4b%5Cu8bd5%5Cu8ba2%5Cu5355%22%2C%22out_trade_no%22%3A%222019080716060001%22%2C%22total_amount%22%3A0.01%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay¬ify_url=http%3A%2F%2Fxx.ip.ip.xx%3A8000%2F&return_url=http%3A%2F%2Fxx.ip.ip.xx%3A8000%2F&sign_type=RSA2×tamp=2019-08-07+16%3A09%3A25&version=1.0&sign=crNYPmSRAccnEb%2BnvnYqgG6qpp4n5NrOHP4sBLyjNBWws6RWS5JrGntGX%2FG2SGqf21dIwvUtt5sV5XY%2Bol1dId%2Bn%2BVBykzJShjB4Y0mt%2Bgm498Tv5ecUCUFvOFXY%2BpWRu3HiuuiJXxCHHzEZ795sw1x8xSQaKZCTEHCBZsfwexKwE1UKsCWLv1cfgjO3O8rCMziSASTMta%2BlfmPcZTdO9tTI9qTXE%2Bq2TMQpZWqBZvN1LPKHdzv1TZL3efjI64qEKglYK6KUCtUgNJoUBJrmYj4Ao3XZMro06Lu73MTPpheg8v56yBXGe4FyMdpxOvOS2t%2FnRZtyM2cx5io6lUoScQ%3D%3D

浏览器访问该链接可以看到沙箱支付页面

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第1张图片

可以使用登录账户付款(在开放平台-沙箱环境-沙箱账号中),也可以下载支付宝沙箱测试应用进行扫码支付。

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第2张图片

支付完成后就会停留在该界面,如果想要跳回商户界面,需要配置return_url

if __name__ == "__main__":
    print(get_server_ip())
    server_ip = get_server_ip()  # 得到自己服务器的IP地址,也就是之前同步项目上去的
    alipay = AliPay(
        app_id="2016100900646609",  # 自己支付宝沙箱 APP ID
        notify_url="http://{}:8000/".format(server_ip),
        app_private_key_path="../apps/trade/keys/private_key_2048.txt",  # 可以使用相对路径那个
        alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        debug=True,  # 默认False,
        return_url="http://{}:8000/".format(server_ip)
    )

    # 创建订单
    url = alipay.direct_pay(
        subject="测试订单",
        out_trade_no="2019080716060003",
        total_amount=0.01,
        return_url="http://{}:8000/".format(server_ip)  # 支付完成后自动跳回该url
    )
    re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    print(re_url)

现在修改订单号测试,如果不修改,支付宝会提示该订单已支付。

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第3张图片

支付完成会自动跳转到return_url="http://{}:8000/".format(server_ip)配置的地址,并且传入很多参数

http://xx.ip.ip.xx:8000/?charset=utf-8&out_trade_no=2019080716060003&method=alipay.trade.page.pay.return&total_amount=0.01&sign=Ej1B3IHs4q6aaWXLqwphXelaPcrok%2Ft7FPmFwns368fc6LcDfvLgBFAqsVwryWMJmHddwiiGgDDSkJKl0f30dYyS0mh9HXlbAXacUJgHHRUvsrN9BiJ9GBoLf%2FInKXcC6n7bqkSDORW1Rnn2N1354NieHVW9W67dNhHCN%2FnpVzALX2c%2F5g7z06i1aYIdLVDfUsXVzn2fDGhZ%2BAHYoFoDKMTr0kFuPGSNPnt8%2F0Q75je8%2BbWmF%2BXMOm1152wVHUMyL1LEO9n9NDba1IPTLWBzlw11CM4eFIIZrTWheCKuACrrNAGDLDSMtDIKwsOWuGY8Z1DCODerGR8Mqbprx%2BSGLQ%3D%3D&trade_no=2019080722001421571000030866&auth_app_id=2016100900646609&version=1.0&app_id=2016100900646609&sign_type=RSA2&seller_id=2088102178762103×tamp=2019-08-07+16%3A31%3A37

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第4张图片

分析return_url和notify_url

当用户创建好订单之后,跳转到支付宝页面进行支付,如果用户通过扫码或登录支付宝,就会创建一个支付宝订单,确认支付后,支付宝会自动跳转到return_url配置的商户页面,我们可以获取到url中的参数,来验证用户是否已经支付,如果支付就修改订单状态。

还有一种情况就是创建订单,通过扫码,或登录支付宝创建支付宝订单后,用户并没有确认支付,而是关闭了该页面,在手机上个人订单中去支付,那么return_url就无效了,而此时,我们的应用就无法判断该订单是否已支付。这时候notify_url就有用了,支付宝会通过异步方式,向该url发起一个请求(POST),并传递一些参数,我们的应用获取参数,解析其中的信息,对订单状态进行修改。

验证支付宝返回的数据

用户支付后,会跳回return_url,我们需要对url进行验证,判定用户是否确认时已支付的,因为数据在传输过程中可能会被截获修改,如果不去做验证,系统上修改订单状态后,却没收到款。

获取url中所有参数,通过支付宝公钥解密

if __name__ == "__main__":
    print(get_server_ip())
    server_ip = get_server_ip()  # 得到自己服务器的IP地址,也就是之前同步项目上去的
    alipay = AliPay(
        app_id="2016100900646609",  # 自己支付宝沙箱 APP ID
        notify_url="http://{}:8000/".format(server_ip),
        app_private_key_path="../apps/trade/keys/private_key_2048.txt",  # 可以使用相对路径那个
        alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        debug=True,  # 默认False,
        return_url="http://{}:8000/".format(server_ip)
    )

    # 创建订单
    # url = alipay.direct_pay(
    #     subject="测试订单",
    #     out_trade_no="2019080716060003",
    #     total_amount=0.01,
    #     return_url="http://{}:8000/".format(server_ip)  # 支付完成后自动跳回该url
    # )
    # re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    # print(re_url)

    # 支付成功后跳回的页面url
    return_url = "http://xx.ip.ip.xx:8000/?charset=utf-8&out_trade_no=2019080716060003&method=alipay.trade.page.pay.return&total_amount=0.01&sign=Ej1B3IHs4q6aaWXLqwphXelaPcrok%2Ft7FPmFwns368fc6LcDfvLgBFAqsVwryWMJmHddwiiGgDDSkJKl0f30dYyS0mh9HXlbAXacUJgHHRUvsrN9BiJ9GBoLf%2FInKXcC6n7bqkSDORW1Rnn2N1354NieHVW9W67dNhHCN%2FnpVzALX2c%2F5g7z06i1aYIdLVDfUsXVzn2fDGhZ%2BAHYoFoDKMTr0kFuPGSNPnt8%2F0Q75je8%2BbWmF%2BXMOm1152wVHUMyL1LEO9n9NDba1IPTLWBzlw11CM4eFIIZrTWheCKuACrrNAGDLDSMtDIKwsOWuGY8Z1DCODerGR8Mqbprx%2BSGLQ%3D%3D&trade_no=2019080722001421571000030866&auth_app_id=2016100900646609&version=1.0&app_id=2016100900646609&sign_type=RSA2&seller_id=2088102178762103×tamp=2019-08-07+16%3A31%3A37"

    # 根据返回的链接,测试验证签名
    o = urlparse(return_url)
    query = parse_qs(o.query)

    # 获取url返回的签名sign
    ali_sign = query.pop("sign")[0]

    processed_query = {}
    for key, value in query.items():
        print('{}:{}'.format(key, value))
        processed_query[key] = value[0]
        # 可以提取到响应参数值
        """
        charset:['utf-8']
        out_trade_no:['2019080716060003']
        method:['alipay.trade.page.pay.return']
        total_amount:['0.01']
        trade_no:['2019080722001421571000030866']
        auth_app_id:['2016100900646609']
        version:['1.0']
        app_id:['2016100900646609']
        sign_type:['RSA2']
        seller_id:['2088102178762103']
        timestamp:['2019-08-07 16:31:37']
        """
    print(alipay.verify(processed_query, ali_sign))

最终如果返回为True表明确认用户已支付,否则返回False,如果我们修改支付宝公钥内容,或者返回的url参数有误,是不能验证成功的。

支付宝支付逻辑编写

由于return_url时一个同步GET请求,notify_url是一个异步POST请求,可以将其放在一个API中实现,跟支付宝相关,没有model,所以就用最底层的APIView

对于return_url,用户支付完成后,支付宝会根据API中商户传入的return_url参数,通过GET请求的形式将部分支付结果参数通知到商户系统。

notify_url很重要,只要用户完成支付,就向该url发起一个异步请求,告诉用户已支付。

在 支付宝开放平台文档中心-电脑网站支付 中,支付结果异步通知 https://docs.open.alipay.com/270/105902/ 文档可以看到相关参数

对于 PC 网站支付的交易,在用户支付完成之后,支付宝会根据 API 中商户传入的 notify_url,通过 POST 请求的形式将支付结果作为参数通知到商户系统。

在 apps/trade/views.py 增加一个API视图,用于处理支付宝的返回

from rest_framework.views import APIView


class AliPayView(APIView):
    def get(self, request):
        """
        处理支付宝return_url返回
        :param request:
        :return:
        """
        pass

    def post(self, request):
        """
        处理支付宝notify_url异步通知
        :param request:
        :return:
        """
        pass

在 DjangoOnlineFreshSupermarket/urls.py 添加该视图AliPayView的url

from trade.views import AliPayView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api-auth/', include('rest_framework.urls')),  # drf 认证url
    path('api-token-auth/', views.obtain_auth_token),  # drf token获取的url
    # path('api/token/', simplejwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'),  # simplejwt认证接口
    path('login/', simplejwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'),  # 登录一般是login
    path('api/token/refresh/', simplejwt_views.TokenRefreshView.as_view(), name='token_refresh'),  # simplejwt认证接口
    path('ckeditor/', include('ckeditor_uploader.urls')),  # 配置富文本编辑器url

    path('', include(router.urls)),  # API url现在由路由器自动确定。

    # DRF文档
    path('docs/', include_docs_urls(title='DRF文档')),

    # 支付宝通知接口
    path('alipay/return/', AliPayView.as_view(), name='alipay')
]

这儿一定要使用AliPayView.as_view(),基于类的视图加上.as_view()

完成notify_url逻辑处理

编写该接口,用户处理支付宝支付后,异步通知接口。首先来确认下,支付宝完成支付后,是否有请求访问到该接口。

先修改 utils/alipay.py 将return_urlnotify_url都配置成上面刚创建的url,然后修改下新的订单号

if __name__ == "__main__":
    print(get_server_ip())
    server_ip = get_server_ip()  # 得到自己服务器的IP地址,也就是之前同步项目上去的
    alipay = AliPay(
        app_id="2016100900646609",  # 自己支付宝沙箱 APP ID
        notify_url="http://{}:8000/alipay/return/".format(server_ip),
        app_private_key_path="../apps/trade/keys/private_key_2048.txt",  # 可以使用相对路径那个
        alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        debug=True,  # 默认False,
        return_url="http://{}:8000/alipay/return/".format(server_ip)
    )

    # 创建订单
    url = alipay.direct_pay(
        subject="测试订单",
        out_trade_no="2019080716060009",
        total_amount=0.01,
        return_url="http://{}:8000/alipay/return/".format(server_ip)  # 支付完成后自动跳回该url
    )
    re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    print(re_url)

将所有代码同步到服务器上去,如果不同步,启动的是服务器上未修改的代码,结果是不一样的,现在把所有代码全都upload to,点击项目选择后上传。

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第5张图片

将运行配置设置为服务器的环境后

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第6张图片

重新Debug启动,将post方法标记为断点

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第7张图片

直接运行 utils/alipay.py ,会输出一个链接,在浏览器中访问该链接并进行支付,支付完成后,后台进入Debug,查看request的内容

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑_第8张图片

拿到这个sign后,可以通过支付宝的公钥进行验签,验证是否是支付宝发过来的。

可以访问 https://docs.open.alipay.com/270/105902/ 查看参数

异步通知参数-公共参数

参数 参数名称 类型 必填 描述 范例
notify_time 通知时间 Date 通知的发送时间。格式为yyyy-MM-dd HH:mm:ss 2015-14-27 15:45:58
notify_type 通知类型 String(64) 通知的类型 trade_status_sync
notify_id 通知校验ID String(128) 通知校验ID ac05099524730693a8b330c5ecf72da978
charset 编码格式 String(10) 编码格式,如utf-8、gbk、gb2312等 utf-8
version 接口版本 String(3) 调用的接口版本,固定为:1.0 1.0
sign_type 签名类型 String(10) 签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2
sign 签名 String(256) 请参考异步返回结果的验签 601510b7970e52cc63db0f44997cf70e
auth_app_id 授权方的app_id String(32) 授权方的appid,由于本接口暂不开放第三方应用授权,因此auth_app_id=app_id 2014072300007148

拿到 sign 签名之后,验证是否是支付宝发来的。

异步通知参数-业务参数

参数 参数名称 类型 必填 描述 范例
trade_no 支付宝交易号 String(64) 支付宝交易凭证号 2013112011001004330000121536
app_id 开发者的app_id String(32) 支付宝分配给开发者的应用 ID 2014072300007148
out_trade_no 商户订单号 String(64) 原支付请求的商户订单号 6823789339978248
trade_status 交易状态 String(32) 交易目前所处的状态,见交易状态说明 TRADE_CLOSED

业务参数中 out_trade_no 可以获取商户订单号,通过该订单号,可以将数据库该订单状态进行修改。

支付宝交易状态说明

枚举名称 枚举说明
WAIT_BUYER_PAY 交易创建,等待买家付款
TRADE_CLOSED 未付款交易超时关闭,或支付完成后全额退款
TRADE_SUCCESS 交易支付成功
TRADE_FINISHED 交易结束,不可退款

支付宝交易状态同步订单状态值

可以将这些状态定义到订单信息model中,修改 apps/trade/models.py 中的OrderInfo

class OrderInfo(models.Model):
    """
    订单
    """
    # ORDER_STATUS = (
    #     ('success', '成功'),
    #     ('cancel', '取消'),
    #     ('topaid', '待支付')
    # )
    ORDER_STATUS = (
        ('TRADE_FINISHED', '交易完成'),
        ('TRADE_SUCCESS', '支付成功'),
        ('WAIT_BUYER_PAY', '交易创建'),
        ('TRADE_CLOSE', '交易关闭')
    )

    # 字段省略
    pay_status = models.CharField(choices=ORDER_STATUS, default='WAIT_BUYER_PAY', max_length=20, verbose_name='订单状态', help_text='订单状态')
    # 字段省略

Vue中同步订单状态值

在 src/views/member/order.vue 订单列表页面,进行以下修改

待支付
已支付

修改为

待支付
已支付

在 src/views/member/orderDetail.vue 订单详情页面也做同样的修改

待支付    
已支付

修改为

待支付    
已支付

支付宝参数配置保存在settings.py

可以将支付宝相关的配置保存在 DjangoOnlineFreshSupermarket/settings.py 文件中,其他文件直接调用即可,无需重复写多次,如果后面配置有变化,直接修改一个地方即可,比如从沙箱转移到正式环境后,只需要将alipay_debug = False即可

# 支付宝相关配置
app_id = "2016100900646609"
alipay_debug = True
app_private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/private_key_2048.txt')
alipay_public_key_path = os.path.join(BASE_DIR, "apps/trade/keys/alipay_key_2048.txt")

notify_url验签处理订单状态

修改 apps/trade/views.py 中的AliPayView

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.alipay import AliPay, get_server_ip
from DjangoOnlineFreshSupermarket.settings import app_id, alipay_debug,  alipay_public_key_path, app_private_key_path
from django.utils import timezone


class AliPayView(APIView):
    def get(self, request):
        """
        处理支付宝return_url返回
        :param request:
        :return:
        """
        pass

    def post(self, request):
        """
        处理支付宝notify_url异步通知
        :param request:
        :return:
        """
        processed_dict = {}
        for key, value in request.POST.items():
            processed_dict[key] = value

        print('request.POST的值:', processed_dict)
        sign = processed_dict.pop('sign', None)  # 直接就是字符串了

        server_ip = get_server_ip()
        alipay = AliPay(
            app_id=app_id,  # 自己支付宝沙箱 APP ID
            notify_url="http://{}:8000/alipay/return/".format(server_ip),
            app_private_key_path=app_private_key_path,  # 可以使用相对路径那个
            alipay_public_key_path=alipay_public_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=alipay_debug,  # 默认False,
            return_url="http://{}:8000/alipay/return/".format(server_ip)
        )

        verify_result = alipay.verify(processed_dict, sign)  # 验证签名,如果成功返回True
        if verify_result:
            order_sn = processed_dict.get('out_trade_no')  # 原支付请求的商户订单号
            trade_no = processed_dict.get('trade_no')  # 支付宝交易凭证号
            trade_status = processed_dict.get('trade_status')  # 交易目前所处的状态

            # 更新数据库订单状态
            OrderInfo.objects.filter(order_sn=order_sn).update(
                trade_no=trade_no,  # 更改交易号
                pay_status=trade_status,  # 更改支付状态
                pay_time=timezone.now()  # 更改支付时间
            )
            # 给支付宝返回一个消息,证明已收到异步通知
            # 当商户收到服务器异步通知并打印出 success 时,服务器异步通知参数 notify_id 才会失效。
            # 也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出 success 导致支付宝重发数次通知),服务器异步通知参数 notify_id 是不变的。
            return Response('success')

当验证成功后return Response('success'),否则支付宝会重复发通知。

完成return_url逻辑处理

对比下POST和GET中的内容

[08/Aug/2019 13:25:45] "POST /orderinfo/ HTTP/1.1" 201 12954
request.POST的值: {'gmt_create': '2019-08-08 13:26:12', 'charset': 'utf-8', 'gmt_payment': '2019-08-08 13:26:20', 'notify_time': '2019-08-08 13:26:21', 'subject': '生鲜超市-20190808132544135', 'sign': 'DkQqkJwi5BnNKdlaVrybA0oLC9wL7aD61aV7vaF6jF6KrFsdDKAS+fS8Y2MQuKqAcPobsM/8ab3FVo7diRnEwXckugP8m6KW5TxFnnPwDh6J3dbNDUNyXTWYKiHCAKMvsV4ZuM7YkJQEGGbs2irf90uM8kxvOaZBGuI6D8QPhc/o6CyCYUJgJU4Zvs6DFuu9Wo1JwldnA9K4E9dd5UJpLI5KmIP6OTjZ13EcoXaslRgjcYdDAj21+cqCpwuD5+EGOuO+6/7T/WMgNSjPPy+cSehVBI9GnhuM7WmH2IQafR+510BLgN12agv4DJB+E1dZAibFAMJFA3Gn/cKDPPjqeQ==', 'buyer_id': '2088102179421571', 'invoice_amount': '10.00', 'version': '1.0', 'notify_id': '2019080800222132620021571000416125', 'fund_bill_list': '[{"amount":"10.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': '20190808132544135', 'total_amount': '10.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2019080822001421571000029372', 'auth_app_id': '2016100900646609', 'receipt_amount': '10.00', 'point_amount': '0.00', 'app_id': '2016100900646609', 'buyer_pay_amount': '10.00', 'sign_type': 'RSA2', 'seller_id': '2088102178762103'}
[08/Aug/2019 13:26:22] "POST /alipay/return/ HTTP/1.1" 200 9
request.GET的值: {'charset': 'utf-8', 'out_trade_no': '20190808132544135', 'method': 'alipay.trade.page.pay.return', 'total_amount': '10.00', 'sign': 'FJputH1jTssknTDV1C5OUOkk3mFNK8qQj5EdszFrizxPmOvS5++v0Apl8ajggksgwlDk20yF/JwI3CdozpnErxZ/4pDMdU2I2A99vXb55akbVPJK6T9/YOo10HC/X+ctctm63ew/vRBIcxtP+BTgj7U+TvKwGkCdy0ZxRm1Ja9GJOwE8Nb4qdp0BQo4hFQ96QNVb0tQ9wTPe6R3qdjLdfyqHhj+GnILxBBehOSHJmDzsQMeMrKhOdY9FtXj21b8aW1YKNulhIU7C8TeUAifu3khmocNMwH+iv9A/hhlqrWjCvlCDUi1GDNRm1W9PCHl+vV6XIitetIOOOsmLlAfq2A==', 'trade_no': '2019080822001421571000029372', 'auth_app_id': '2016100900646609', 'version': '1.0', 'app_id': '2016100900646609', 'sign_type': 'RSA2', 'seller_id': '2088102178762103', 'timestamp': '2019-08-08 13:26:27'}
class AliPayView(APIView):
    def get(self, request):
        """
        处理支付宝return_url返回
        :param request:
        :return:
        """
        processed_dict = {}
        for key, value in request.GET.items():  # GET逻辑和POST基本一样
            processed_dict[key] = value

        print('request.GET的值:', processed_dict)
        sign = processed_dict.pop('sign', None)  # 直接就是字符串了

        server_ip = get_server_ip()
        alipay = AliPay(
            app_id=app_id,  # 自己支付宝沙箱 APP ID
            notify_url="http://{}:8000/alipay/return/".format(server_ip),
            app_private_key_path=app_private_key_path,  # 可以使用相对路径那个
            alipay_public_key_path=alipay_public_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=alipay_debug,  # 默认False,
            return_url="http://{}:8000/alipay/return/".format(server_ip)
        )

        verify_result = alipay.verify(processed_dict, sign)  # 验证签名,如果成功返回True
        if verify_result:
            # POST中已经修改数据库订单状态,无需再GET中修改,且,GET中也得不到支付状态值

            # 给支付宝返回一个消息,证明已收到异步通知
            return Response('success')

    def post(self, request):
        # 。。。

我们也可以只传递notify_url,不传递return_url,但是有些情况下,我们希望支付成功后,可以返回到商户页面,比如个人订单中心,这就需要传递return_url

当前在return_url的GET逻辑中,可以不用再次修改订单状态,因为在用户支付成功后,支付宝发送的POST请求已经修改了订单状态了。

你可能感兴趣的:(Web开发)