• 微信APP支付V3版本签名 && APP下单/订单查询接口Python版实现


    问题背景

    最近接入微信支付,微信官方并没有提供Python版的服务端SDK,因而只能根据文档手动实现一版,这里记录一下微信支付的整体流程、踩坑过程与最终具体实现。

    微信支付APP下单流程

    根据微信官方文档: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_2.shtml
    下单流程如下:
    image
    和支付宝不同,微信多了一个预付单的概念,这里把APP下单实际分为四大部分,其中包含请求微信后端需要的首次签名和需要返回给APP的二次支付信息签名--这里踩一个小坑,流程图中并没把第二次签名支付信息需要返回给APP的步骤画出来(即下面的步骤6.5),因而一开始误以为只需要返回prepay_id给客户端,导致校验失败。
    一. 对应步骤1~4,APP 请求业务后端,业务后台进行V3签名后,请求微信后端生成预付单prepay_id
    二. 对应步骤5~6.5,业务后端收到微信后端返回prepay_id,将支付相关参数打包进行二次签名后返回给APP,这里相比流程图多了一个6.5--即业务后端返回签名支付信息到APP
    三. 对应步骤7~18,APP收到业务后端返回签名支付信息后调起SDK发起支付请求,收到同步消息结果通知
    四. 对应步骤19~22,APP查询业务后端,业务后端通过回调通知或直接查询微信后端返回最终支付结果

    代码实现

    首次签名逻辑

    第一次请求生成预付单号的签名文档为:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml, 共5个部分参与签名,其组成格式为:

    HTTP请求方法\nURL\n请求时间戳\n请求随机串\n请求报文主体\n
    

    对应签名代码:

    class WechatPayDALBase(object):
        def __init__(self, mch_appid, mchid, v3key, serial_no, client_key):
            self.mch_appid = mch_appid
            self.mchid = mchid
            self.v3key = v3key
            # serial_no可通过openssl直接获取, 例: openssl x509 -in 1900009191_20180326_cert.pem -noout -serial
            self.serial_no = serial_no
    
            with open(client_key, 'r') as ifile:
                pkey = RSA.importKey(ifile.read())
            self.signer = pkcs1_15.new(pkey)
    
        def compute_sign_v3(self, method, url, body):
            '''
            V3签名逻辑
            '''
            ts = int(time.time())
            nonce = self.generate_nonce()
            uparts= parse_url(url)
            ustr = uparts.path + ('?{}'.format(uparts.query) if uparts.query else '')
            content = '{}\n{}\n{}\n{}\n{}\n'.format(method, ustr, ts, nonce, body)
    
            digest = SHA256.new(content.encode('utf-8'))
            sign_v = base64.b64encode(self.signer.sign(digest)).decode('utf-8')
            sign_str = 'serial_no="{}",mchid="{}",timestamp="{}",nonce_str="{}",signature="{}"'.format(
                        self.serial_no, self.mchid, ts, nonce, sign_v)
            return sign_str
    
        def make_headers_v3(self, url, headers=None, body='', method='GET'):
            '''
            微信支付V3版本签名header生成函数
            '''
            if not headers:
                headers = {}
            headers['Accept'] = 'application/json'
            sign = self.compute_sign_v3(method, url, body)
            auth_info = 'WECHATPAY2-SHA256-RSA2048 {}'.format(sign)
            headers['Authorization'] = auth_info
            return headers
    
        def generate_nonce(self):
            rnd = int(time.time()) + random.randint(100000, 1000000)
            nonce = hashlib.md5(str(rnd).encode()).hexdigest()[:16]
            return nonce
    

    二次签名逻辑

    由业务后端返回给APP的二次签名信息文档为:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
    共4个部分参与签名,其组成格式为:

    应用id\n时间戳\n随机字符串\n预支付交易会话ID\n
    

    返回签名支付信息的对应代码:

        def get_pay_sign_info(self, prepay_id):
            ts = int(time.time())
            nonce = self.generate_nonce()
            content = '{}\n{}\n{}\n{}\n'.format(self.mch_appid, ts, nonce, prepay_id)
    
            digest = SHA256.new(content.encode('utf-8'))
            sign_v = base64.b64encode(self.signer.sign(digest)).decode('utf-8')
            return {
                'appid': self.mch_appid,
                'partnerid': self.mchid,
                'timestamp': str(ts),
                'noncestr': nonce,
                'prepay_id': prepay_id,
                'package': 'Sign=WXPay',
                'sign': sign_v,
            }
    

    业务后端查询订单详情

    文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_2.shtml
    代码如下:

        def query_order(self, out_trade_no):
            '''
            查询指定订单信息
            '''
            url = f'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}?mchid={self.mchid}'
            headers = self.make_headers_v3(url)
            rsp = requests.get(url, headers=headers)
            pay_logger.info('out_trade_no:{}, rsp:{}|{}'.format(out_trade_no, rsp.status_code, rsp.text))
            rdct = rsp.json()
            return rdct
    

    业务后端调用APP下单API

    文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
    代码如下:

        def create_order_info(self, data, callback_url):
            '''
            创建微信预支付订单, 注意包含两次签名过程:
            首次签名用于请求微信后端获取prepay_id
            二次签名信息返回客户端用于调起SDK支付
            '''
            url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/app'
            ndt = datetime.now()
            out_trade_no = self.generate_partner_trade_no(ndt)
            data = {
                'mchid': self.mchid,
                'out_trade_no': out_trade_no,
                'appid': self.mch_appid,
                'description': data['subject'],
                'notify_url': callback_url,
                'amount': {
                    'currency': 'CNY',
                    'total': int(data['price']),
                },
                'time_expire': (ndt + timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%S+08:00')
            }
            jdata = json.dumps(data, separators=[',', ':'])
            headers = {'Content-Type': 'application/json'}
            # 第一次签名, 直接请求微信后端
            headers = self.make_headers_v3(url, headers=headers, body=jdata, method='POST')
            rsp = requests.post(url, headers=headers, data=jdata)
            pay_logger.info('rsp:{}|{}'.format(rsp.status_code, rsp.text))
            rdct = rsp.json()
            # 第二次签名, 返回给客户端调用
            sign_info = self.get_pay_sign_info(rdct['prepay_id'])
            return sign_info
    

    源码地址

    试水代码开源,把相关代码分享在了github:https://github.com/liuzhi67/wechat-pay-python

    转载请注明出处,原文地址:https://www.cnblogs.com/AcAc-t/p/wechat_pay_by_python.html

  • 相关阅读:
    STL模板库笔记
    如何在 Visual Studio Code 中使用 Prettier 格式化代码
    WPF 项目开发入门(七) From表单组件
    信息抽取/实体关系抽取之UIE
    STM32CUBEIDE(14)----外部中断EXTI
    服务器----阿里云服务器重启或关机,远程连接进不去,个人博客无法打开
    京东医疗器械分类汇总
    Java中的常量管理:接口还是枚举,您如何选择?
    电子招标采购商城系统:优化传统采购业务,提速企业数字化升级
    Java高级编程—注解
  • 原文地址:https://www.cnblogs.com/AcAc-t/p/wechat_pay_by_python.html