小程序支付

一、小程序支付官方文档

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1 # 接口

二、小程序支付流程

1 用户发起请求下单支付
2 我们要保证用是登入状态。
3 组织数据,请求统一下单接口,微信官方会同步返回一个prepay_id
4 重新组织数据,进行签名,将重新组织的数据返回给小程序,小程序在吊起支付。
5 用户就可以进行支付,支付结果会同步返回给小程序
6 后台修改订单支付状态是通过微信官方服务器的异步通知

三、支付流程图(下方有接口)

业务流程时序图

小程序支付的交互图如下:

小程序支付时序图

商户系统和微信支付系统主要交互:

1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API

2、商户server调用支付统一下单,api参见公共api【统一下单API

3、商户server调用再次签名,api参见公共api【再次签名

4、商户server接收支付通知,api参见公共api【支付结果通知API

5、商户server查询支付结果,api参见公共api【查询订单API

四、签名

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3

# 签名
    def get_sign(self):
        data_dict = {
            "appid": self.appid,
            "mch_id": self.mch_id,
            "nonce_str": self.nonce_str,
            "body": self.body,
            "out_trade_no": self.out_trade_no,
            "total_fee": self.total_fee,
            "spbill_create_ip": self.ip,
            "notify_url": self.notify_url,
            "trade_type": self.trade_type,
            "openid": self.openid,
        }
        # 列表推导式,
        sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
        sign_str = f"{sign_str}&key={settings.pay_apikey}"
        print("sign_str", sign_str)
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        sign = md5.hexdigest()
        return sign.upper()

五、xml解析模块

<xml>
  <appid name="属性值" >{.child.text}</appid>
   child.tag表示appid   
</xml> 

import xml.etree.ElementTree as ET

如果我们要解析一个xml文件
tree = ET.parse('country_data.xml')
root = tree.getroot()

如果解析字符串
root = ET.fromstring(country_data_as_string)

这个root是 Element 
for child in root:
	 print(child.tag, child.attrib)
	 #child.tag表是标签名,child.attrib表示获取属性
	 #child.text就表示获取内容

# 拼接的xml数据
body_data = f'''
    <xml>
    <appid>{self.appid}</appid>
    <mch_id>{self.mch_id}</mch_id>
    <nonce_str>{self.nonce_str}</nonce_str>
    <body>{self.body}</body>
    <out_trade_no>{self.out_trade_no}</out_trade_no>
    <total_fee>{self.total_fee}</total_fee>
    <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip>
    <notify_url>{self.notify_url}</notify_url>
    <trade_type>{self.trade_type }</trade_type>
    <openid>{self.openid }</openid>      
    <sign>{self.sign}</sign>      
    </xml> 
'''

接收xml二进制数据,转换为字典

# 接收xml二进制数据,转换为字典
    def xml_to_dict(self, xml_data):
        import xml.etree.ElementTree as ET

        xml_dict = {}

        root = ET.fromstring(xml_data)

        for child in root:
            xml_dict[child.tag] = child.text
        return xml_dict

六、再次签名

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3

# 再次签名
    def get_two_sign(self, data):
        data_dict = {
            "appId": settings.AppId,
            "timeStamp": str(int(time.time())),
            "nonceStr": data['nonce_str'],
            "package": f"prepay_id={data['prepay_id']}",
            "signType": "MD5"

        }
        sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
        sign_str = f"{sign_str}&key={settings.pay_apikey}"
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        sign = md5.hexdigest()
        return sign.upper(), data_dict['timeStamp']

七、前台吊起支付接口

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3

wx.requestPayment(
    {
    'timeStamp':e.data.data.timeStamp,
    'nonceStr': e.data.data.nonceStr,
    'package': e.data.data.package,
    'signType': e.data.data.signType,
    'paySign': e.data.data.paySign,
    'success':function(res){
    console.log("成功",res)
    },
    'fail':function(res){
    console.log("失败",res)
    },

})

八、小程序支付总结

1 接收到支付请求。我们先组织数据,然后进行统一下单前的签名
- 请求的数据与响应的数据都是xml.请求的时候,xml数据要变成二进制,heards中的content-type:"application/xml"
-响应的数据也是xml,我要用xml.etree.ElementTree将他转化为字典
2 拿到统一下单数据,最重要的prepay_id,进行再次签名。把一下数据发送给小程序。
			"timeStamp": 时间戳
            "nonceStr":随机字符串
            "package": f"prepay_id={data_dict['prepay_id']}",统一下单中的到的prepay_id
            "signType": "MD5",
            "paySign":通过上面数据进行加密的结果
 3 小程序掉用wx.resquestPayment()吊起支付

案例

后台

from rest_framework.views import APIView
from rest_framework.response import Response
from django.core.cache import cache
from api.wx import settings
import hashlib
import requests
import time


class Pay(APIView):
    def post(self, request):
        param = request.data
        if param.get("token") and param.get("money"):
            # 根据传过来的token,从缓存中拿出openid_session_key
            openid_session_key = cache.get(param.get("token"))

            # 如果存在,证明传过来的token合法有效
            if openid_session_key:
                if request.META.get('HTTP_X_FORWARDED_FOR'):
                    # 有负载均衡就用这个
                    self.ip = request.META['HTTP_X_FORWARDED_FOR']
                else:
                    # 没有负载均衡就用这个
                    self.ip = request.META['REMOTE_ADDR']

                self.openid = openid_session_key.split("&")[1]
                self.money = param.get("money")
                data = self.get_pay_data()  # 调用支付接口
                return Response({"code": 0, "msg": "ok", "data": data})
            else:
                return Response({"code": 2, "msg": "token无效"})

        else:
            return Response({"code": 1, "msg": "缺少参数"})

    # 支付接口
    def get_pay_data(self):
            self.appid = settings.AppId
            self.mch_id = settings.pay_mchid  # 公司商户号,选哟公司三证申请
            self.nonce_str = self.get_nonce_str()  # 生成一个随机数
            self.body = "老男孩学费"  # 商品描述
            self.out_trade_no = self.get_order_id()  # 商户订单号,模拟
            self.total_fee = self.money  # 支付金额
            self.spbill_create_ip = self.ip  # 调用微信支付API的机器IP
            self.notify_url = "htttp://www.test.com"  # 通知地址
            self.trade_type = "JSAPI"  # 交易类型
            self.openid = self.openid  # 用户标识
            self.sign = self.get_sign()  # 签名

            # 拼接的xml数据
            body_data = f'''
                <xml>
                    <appid>{self.appid}</appid>
                    <mch_id>{self.mch_id}</mch_id>
                    <nonce_str>{self.nonce_str}</nonce_str>
                    <body>{self.body}</body>
                    <out_trade_no>{self.out_trade_no}</out_trade_no>
                    <total_fee>{self.total_fee}</total_fee>
                    <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip>
                    <notify_url>{self.notify_url}</notify_url>
                    <trade_type>{self.trade_type }</trade_type>
                    <openid>{self.openid }</openid>      
                    <sign>{self.sign}</sign>      
                </xml> 
                '''
            url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
            # 如果发送的xml数据要把数据转化二进制。body_data.encode("utf-8")
            # headers={"content-type": "application/json"}   json数据
            # headers={"content-type": "application/xml"}    xml数据
            response = requests.post(url, data=body_data.encode("utf-8"), headers={"content-type": "application/xml"})
            # 接收一个二进制的响应,调接口
            data_dict = self.xml_to_dict(response.content)
            print('data_dict:', data_dict)
            # 再次签名
            pay_sign, timeStamp = self.get_two_sign(data_dict)
            data = {
                "timeStamp": timeStamp,
                "nonceStr": data_dict['nonce_str'],
                "package": f"prepay_id={data_dict['prepay_id']}",
                "signType": "MD5",
                "paySign": pay_sign
            }
            return data

    # 随机字符串
    def get_nonce_str(self):
        import random
        data = "123456789abcdefghijklmn"
        nonce_str = "".join(random.sample(data, 10))
        # random.sample(从哪里取,取多小个),变成列表
        return nonce_str

    # 模拟订单号
    def get_order_id(self):
        import time
        import random
        data = "123456789abcdefghijklmn"
        order_no = str(time.strftime("%Y%m%d%H%M%S")) + "".join(random.sample(data, 5))
        return order_no

    # 签名
    def get_sign(self):
        data_dict = {
            "appid": self.appid,
            "mch_id": self.mch_id,
            "nonce_str": self.nonce_str,
            "body": self.body,
            "out_trade_no": self.out_trade_no,
            "total_fee": self.total_fee,
            "spbill_create_ip": self.ip,
            "notify_url": self.notify_url,
            "trade_type": self.trade_type,
            "openid": self.openid,
        }
        # 列表推导式,
        sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
        sign_str = f"{sign_str}&key={settings.pay_apikey}"
        print("sign_str", sign_str)
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        sign = md5.hexdigest()
        return sign.upper()

    # 接收xml二进制数据,转换为字典
    def xml_to_dict(self, xml_data):
        import xml.etree.ElementTree as ET

        xml_dict = {}

        root = ET.fromstring(xml_data)

        for child in root:
            xml_dict[child.tag] = child.text
        print('xml_dict:',xml_dict)
        return xml_dict

    # 再次签名
    def get_two_sign(self, data):
        data_dict = {
            "appId": settings.AppId,
            "timeStamp": str(int(time.time())),
            "nonceStr": data['nonce_str'],
            "package": f"prepay_id={data['prepay_id']}",
            "signType": "MD5"

        }
        sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
        sign_str = f"{sign_str}&key={settings.pay_apikey}"
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        sign = md5.hexdigest()
        return sign.upper(), data_dict['timeStamp']


前台

 pay:function(){
    wx.request({
      url: app.globalData.baseurl + "pay/",
      //支付的钱,和携带的token,1代表1分钱
      data: { "money": 1, token: wx.getStorageSync('token') },
      method: "POST",
      success(e){
        console.log('支付数据',e)

        wx.requestPayment(
          {
            'timeStamp': e.data.data.timeStamp,
            'nonceStr': e.data.data.nonceStr,
            'package': e.data.data.package,
            'signType': e.data.data.signType,
            'paySign': e.data.data.paySign,
            'success': function (res) {
              console.log("成功", res)
            },
            'fail': function (res) {
              console.log("失败", res)
            },

          })

      }
    })
  }
posted @ 2020-03-14 08:30  Jeff的技术栈  阅读(1814)  评论(0编辑  收藏  举报
回顶部