IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python - 微信支付开发系列之一 - 辅助函数 -> 正文阅读

[Python知识库]Python - 微信支付开发系列之一 - 辅助函数

【原创】:转载请注明出处,谢谢!

相信大家在微信支付开发过程中,都多多少少遇到过不少的坑,笔者前不久从坑里跳出来,觉得有必要总结和分享一下走过的坑。这是一个系列文章一部分,也是基础概念和流程的介绍。

基本假设

首先,这里有一些基本的假设,

  1. 前后端分离的方式开发,你是后端,开发语言为Python;
  2. 需要开发微信支付;
  3. 你目前需要从小程序调起微信支付以便用户付款;
  4. 你需要以JSAPI的方式开发微信支付(其实其他方式开发也是大同小异)。

微信的坑

坑1:微信支付官方资料太过于混乱,以至于可能看了2-3天的资料,仍然没有头绪;

坑2:基本概念解释的既乏味,又没有营养;

坑3:原来各个支付方式中的概念是相通的!

支付流程

首先介绍一下支付的流程。其实,微信转账很容易完成,不用开发人员,你直接将自己微信的钱转给自己的女朋友,简单的描述如下:

  1. 从通讯录找到女朋友;
  2. 打开聊天窗口,点击"+"号;
  3. 选择转账;
  4. 输入金额,如有需要,添加备注;
  5. 确认转账;
  6. 完成验证,转账成功。

我们的支付开发也是类似的,但是会有如下的不同点:

  1. 收款方式:支付是由收款方发起,而非付款方发起,所以一定要在某个契机之下,发送消息给用户,要求收款;
  2. 时效性:我们的交易可能不是立刻完成的,所以我们需要将说好的交易先保存在某个地方;
  3. 避免干扰不想关用户:我们不能随随便便向所有人都发起收款申请,所以要向微信证明,我们能够找这个用户收款;
  4. 收款有据:我们作为企业方(公司),一定是因为提供了服务或者商品,才有资格收款的。通常公司都会是因为有订单,所以要收款;
  5. 信任问题:我们不是自己登陆在微信客户端,微信不一定信任我们。我们得证明自己是自己;
  6. 安全问题:信息都是在网络传输,被人篡改怎么办,所以得想个办法避免这个问题;
  7. 确认收款的方式:最后,作为公司,不可能专门派人去看着每笔款是否到账,只要微信告诉我们到账,我们就认为到账了。

前置知识

  • 微信为了管理所有的小程序,给所有的小程序都分配了一个唯一的号码(appid),同时,为了识别每个小程序下的用户,都会分配一个在该小程序下唯一的用户号(openid);
  • 前端通常不存储微信发给商户的app key信息(这个注册的时候会有的)。

基于以上的不同点,目前,微信JSAPI支付的流程设计如下:

  1. 获取用户openid:前后端配合完成。这个openid是用来发起收款申请的,既然能够发起收款申请,也就意味着经过了用户同意了,也就意味着客户有一个授权过程。后面弹出支付要求时,我们也不算干扰不相关用户。这个授权的过程,由于我们目前前后端分离的开发(当然,也因为微信基于安全的设计),进一步分解如下:
  • a. 获取code:在用户打开小程序时,小程序(前端wx.login来调用)会弹出授权请求,这个时候,用户一旦授权,前端就会收到临时的用户号,即code,此code仅有5分钟的有效时间;
  • b. 获取openid:前端将临时的用户号(code)传给后端,后端调起相关接口,通过自己的app密码(app key)等获取openid等,具体参考如下:
    官方openid获取指导
  1. 统一下单:后端完成。有了openid,意味着用户授权我们,我们可以"打扰"用户。分头叙述,假如这个时候,用户在小程序看到了好吃的,好产品,或者服务,想占为己有,就会下单,下单时,商户这边会生产一个商户自己的内部订单号,下单后,是否立刻支付,这个就涉及到订单时效性,为了记住订单,以便用户过一会儿付款也不丢失状态,所以,我们找微信下个订单。而这个下单,就是统一下单,为了一一对应,我们将自己的内部订单号给微信。同时,还有一些识别我们身份所需的东西,比如appid,mch_id,app key等等。下面解释一下所必需的东西以及理由:
  • appid,mch_id:与信任问题相关。这些不用说吧,你得告诉微信你是谁,当然光有这个不行,还要有app key,但是app key就像钥匙,不会直接传输的,这个要到最后sign的时候讲;

  • nonce_str:这个是为了安全,提高安全系数;

  • body:这个与收款有据相关。告诉微信,这个订单你提供了什么服务,相当于备忘,其实我们随便填什么微信都接受,但是为了方便后续查询等,避免麻烦等,最好参照官方规范;

  • out_trade_no:这个与收款有据相关。内部订单号,用来和微信的支付订单一一对应的,在公司内部需要唯一,唯一性的要求很好理解,如果一旦用户支付,微信告诉你订单已经支付,你公司内部有两个一样的订单号指向同一个微信的支付订单的话,你怎么知道到底是哪个订单被支付?当然,具体内部订单号什么规则,这个由你来决定,别超过32位,也不要是奇奇怪怪的字符就行,比如ChiPuTaobutuputaopi,5chiputaoNichipi,这些都可以。

  • total_fee:订单金额,本来没什么说,收钱当然要告诉人家收多少,但是有个坑,记得单位为分,所以自然而然传给微信的是整数,否则后面会报错,“0参数错误”什么的。比如,收100元,传过去的数字要10000。

  • spbill_create_ip:这个是微信怕你抵赖,记录下订单的发生地址。这个相当于现实世界里奶茶店的地址,你外卖下单,一定会知道奶茶店的地址,实际是不是从那里发出的不要紧,但是一旦作假,只要微信想知道,查一下就可以了。毕竟,如果你从上海任何地方点一杯奶茶,外卖地址都是从上海人民广场,你查一下所有订单,肯定知道这个有问题。

  • notify_url:这个是用户支付这个订单后,微信通知我们支付结果的地址,即我们通过这里微信返回的信息确认是否收款

    • 首先,既然微信想通知我们,我们如果躲在局域网内,微信是找不到我们的,所以这个地址必须是公网地址;
    • 其次,人家是过来送信的,如果我们在门口安排保安拦着,这样人家就送不过来了,所以,这个地址必须是无守卫的门一样,可以任意接受信件。
  • trade_type:这里是JSAPI。这个参数必传的原因是,这是一个通用的下单接口,为了方便微信收到后,给相应的下道处理程序,所以传这个参数。有点像去菜市场买肉,有几个卖肉(猪肉,牛肉,羊肉)的摊位,你需要指定买哪种肉几斤,不能只说要买几斤肉。

  • openid:注意看备注,当支付方式为JSAPI时,本项是必填项。意思是,在微信系统里,生成的这个支付订单只能找该用户收钱。这里其实是我们的收款方式决定的。

  • sign:这个特意放在最后写,因为这个与以上都相关,且是涉及安全问题的。

    • 这里会用到我们手上的app key,用来证明确实是我们让微信创建待支付订单的,即信任问题
    • 需要这个sign,主要是担心以上这些内容传给微信时,万一传输途中被人篡改了呢?所以将以上所有字段按照一定规则,结合在手上的app key,一起非对称加密(这个概念很大,简单的说,就是一款由各种材料按照一定比例制成的香水,各种材料指的是以上的字段,做出来的香水就是sign)的结果,作为sign传给微信。万一有人截获信息,做任何的篡改,结果都会导致字段信息和sign对不上,微信就会告诉我们,sign不对。

参考如下:
微信统一下单要求
官方关于字段内容的规范要求

  1. 调起支付:这里由前端(WeixinJSBridge.invoke来调起支付)完成。从上一步,我们在微信支付系统中生成了订单,叫做prepay_id,从名字看得出,是待支付订单号。有了他,后端就可以将所有字段传给前端调起支付了。微信为了区分前后端的字段要求,不同于后端调起的带下划线的参数名(nonce_str等),特意将这些参数名字改成小驼峰了,比如timeStamp,nonceStr之类的。单个的单词sign直接改为了paySign,不好改的prepay_id干脆换了个单词package来替代。

官方调起支付的参数

  1. 弹出支付界面:如果以上都顺利,弹出微信支付界面,用户付款。付款OK后,微信会根据我们在上面给的notify_url访问该地址,并且将结果告诉我们。

辅助函数

好了,作为系列之一,我们先来看辅助函数,主要有:

  1. 生成随机数 nonce_str
  2. 生成xml格式:统一下单接口调用规定使用xml格式
  3. 转为字典:接受微信回传的信息,转换成字典
  4. 生成sign string:用于生成sign
  5. 生成sign或者paySign

Talk is cheap,show me your code!

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/8/7 23:06
# @Author  : John Liu
# @File    : wechat_pay.py
# @Software: PyCharm
# @Project : Universal
# @Desc    : wechat pay module

import hashlib
import hmac
import random
import string
from collections import OrderedDict
from lxml import etree

import wechat_setting  # some pre_defined constants


class WechatPay:

    def __init__(self,
                 app_id: str = wechat_setting.APP_ID,
                 mch_id: str = wechat_setting.MCH_ID,
                 secret_key: str = wechat_setting.SECRET_KEY,
                 sign_algorithm_md5: str = wechat_setting.WECHAT_SIGN_ALGORITHM_MD5,
                 pay_notify_url: str = wechat_setting.PAY_NOTIFY_URL,
                 wechat_key: str = wechat_setting.WECHAT_KEY,
                 wechat_prepay_api: str = wechat_setting.WECHAT_PREPAY_API,
                 ):
        self.app_id = app_id
        self.mch_id = mch_id
        self.secret_key = secret_key
        self.pay_notify_url = pay_notify_url
        self.wechat_key = wechat_key
        self.wechat_prepay_api = wechat_prepay_api
        self.sign_algorithm_md5 = sign_algorithm_md5

    def generate_nonce_str(self, length: int = 32) -> str:
        nonce_symbols = string.digits + string.ascii_letters
        return "".join(random.choice(nonce_symbols) for _ in range(length))

    def generate_sign_str(self, request_data: dict, is_pay_sign: bool = False) -> str:
        if is_pay_sign:
            request_data["package"] = f"prepay_id={request_data.pop('prepay_id')}"

        sign_string = ""
        ordered_items = OrderedDict(sorted(request_data.items()))
        for key, value in ordered_items.items():
            if key != "sign" and key != "paySign" and value is not None:
                sign_string = sign_string + f"{key}={value}&"

        return sign_string + f"key={self.wechat_key}"
        
    def create_sign(self, sign_string: str, sign_algorithm: str = "MD5") -> str:
        if sign_algorithm.upper() == self.sign_algorithm_md5:
            signed_string = hashlib.md5(sign_string.encode(encoding='UTF-8')).hexdigest()
        else:
            signed_string = hmac.new(str.encode(self.wechat_key), str.encode(sign_string),
                                     digestmod=hashlib.sha256).hexdigest()
        return signed_string.upper()

    @staticmethod
    def to_xml(raw: dict) -> bytes:
        child_string = ""
        for key, value in raw.items():
            child_string += f"<{key}>{value}</{key}>"
        root = f"<xml>{child_string}</xml>"
        return root.encode("utf-8")

    @staticmethod
    def to_dict(xml_content: str) -> dict:
        raw = {}
        root = etree.fromstring(xml_content,
                                parser=etree.XMLParser(resolve_entities=False))
        for child in root:
            raw[child.tag] = child.text
        return raw

好了,本系列第一期首先到这里,欢迎留言交流,后续有时间再整理其他相关概念,蹲的坑和跳出坑解决方法,证书验证,统一下单等具体支付细节。

喜欢可以点赞收藏哦…

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-08-08 11:17:17  更:2021-08-08 11:18:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/26 1:01:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计