13 小程序支付 (统一下单 再次签名)


前言:小程序支付

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

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

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

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

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

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

支付流程总结:

用户登录点击下单—小程序wx.request请求商户支付接口—商户带上必要参数请求微信支付url — 生成预支付交易单(prepay_id) — 返回正确的预支付交易(再次签名)—调起支付(wx.requestPayment

1)小程序登入API解释

见登录

2)统一下单接口解释

​ 1当用户发起下单请求,且用户是登入状态的情况下,我们就可以创建我们商城的订单了。

​ 2当订单创建完毕,我们应该发起支付,调用支付接口,也就是统一下单接口(这里的下单不是我们商城的下单,而是对微信官方进行支付下单),我们按照微信统一下单接口发送数据,微信下单接口会同步返回数据给我们,也就是上图中prepay_id

3)再次签名

​ 1当我们拿到统一下单的数据以后,我们要再次进行签名(对数据加密以及处理)。

​ 2 然后将数据发送给我们小程序。

​ 3 小程序拿到我们发送的数据后调起支付界面,这样用户就可以进行支付了,如果支付成功,微信会直接返回 结果给我们小程序

4)异步通知接口

​ 1在第三步再次签名中我们小程序已经知道用户是否支付成功,但是我们后端还不知道该订单是否支付成功

​ 2 基于1的问题,微信会以异步的方式通知我们后端程序,我们拿到微信异步通知的数据,我们就可以对订单 进行修改。那这样就实现了,前后都知道用户的支付结果

一、统一下单

这里下单不是我们业务中下单,这里的下单是对我们微信支付接口下单

1.1 接口链接

URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder

携带下面这些必要参数,post请求该url会有返回值

1.2 请求参数

在里面我们只对部分必传字段进行解释,其他字段根据开发的需求而定,详细内容见API

1 appid

​ 我们小程序的唯一标识,在注册的时候我们已经知道了
2 mch_id

​ 我们使用支付,就必须开通支付,以公司的证件开通,开通过后就分配该商户号mch_id

3 nonce_str:随机字符串不能超过32位

​ 代码实现:

# 如果是做验证码的话,不要出现1/i,0/o
def get_random(self):
    import random
    data = "123456789zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP"
    nonce_str = "".join(random.sample(data, 30))
    return nonce_str
View Code

4 sign:签名,期作用是为了防止我们发送的数据陪黑客拦截,根据官方算法,我们这里直接实现:

 def get_sign():
        data_dic = {
            #随机字符串
            "nonce_str": nonce_str,
            #商城订单号,不能重复,大家可以更具公司要求,生成订单号
            "out_trade_no": out_trade_no,
            #客户端请求的ip
            "spbill_create_ip": spbill_create_ip,
            #回调地址,就是微信服务异步通知我们的支付结果时候的url,这个url不能携带参数
            #错误 http://www.test.com/notify_url?name=weixin
            #正确 http://www.test.com/notify_url
            "notify_url": notify_url,
            #用户唯一标识,是那个用户要发起支付
            "openid": open_id,
            #订单表述
            "body": body,
            #固定值,用小程序支付必须传这个JSAPI
            "trade_type": "JSAPI",
            "appid": appid,
            #该次支付所要支付的钱数,是以分为单位,大家要注意
            "total_fee": total_fee,
            "mch_id": mch_id
        }
        #这里pay_apikey是和mch_id一起分配的
        sign_str = "&".join([f"{k}={data_dic[k]}" for k in sorted(data_dic)])
        sign_str = f"{sign_str}&key={pay_apikey}"
        import hashlib
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        sign = md5.hexdigest()
        return sign.upper() 
View Code

最终将数据组织成xml数据。如下:

       # 下面开始生成data,f为参数替换,这一大堆就是data
        data=f"""  
        <xml>
               <appid>{self.appid}</appid>
               <body>{ self.body}</body>
               <mch_id>{self.mch_id}</mch_id>
               <nonce_str>{self.nonce_str}</nonce_str>
               <notify_url>{self.notify_url}</notify_url>
               <openid>{self.openid}</openid>
               <out_trade_no>{self.out_trade_no}</out_trade_no>
               <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip>
               <total_fee>{self.total_fee}</total_fee>
               <trade_type>{self.trade_type}</trade_type>
               <sign>{self.sign}</sign>
        </xml>
        """
xml

注:参数值用XML转义即可,CDATA标签用于说明数据不被XML解析器解析。

1.3 发送数据请求连接

这里最终是以xml的形式发送数据,所以我们发送数据的时候要用heard头中的"content-type:application/xml"

发送请求代码:

    def xml_to_dict(self,data):
        import xml.etree.ElementTree as ET
        xml_dict = {}
        # 将二进制xml数据转换成了xml老母猪
        data_dic = ET.fromstring(data)  # 大概是个xml老母猪 <Element 'xml' at 0x0000000002168EF8>
        # 遍历老母猪,生成真正的字典,key为标签名,val为标签值

        for item in data_dic:
            xml_dict[item.tag] = item.text  # <xml> <appid>123456</appid> </xml>
        return xml_dict  # {'appid': '123456'}

def get_body_data():
    body_data = f"""
           <xml>
               <appid>{appid}</appid>
               <mch_id>{mch_id}</mch_id>
               <nonce_str>{nonce_str}</nonce_str>
               <sign>{sign}</sign>
               <body>{body}</body>
               <out_trade_no>{out_trade_no}</out_trade_no>
               <total_fee>{total_free}</total_fee>
               <spbill_create_ip>{spbill_create_ip}</spbill_create_ip>
               <notify_url>{notify_url}</notify_url>
               <openid>{open_id}</openid>
               <trade_type>JSAPI</trade_type> 
           </xml>"""
    import requests
    url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
    response = requests.post(url, data_body.encode("utf-8"), headers={'content-type': "application/xml"})
    #由于返回的数据也是xml的,我们需要将xml数据转化成字典,
    res_dict =xml_to_dic(response.content)
View Code

1.4 请求的返回数据

详见API

 

 

 返回的数据也是二进制的xml我们需要将其转化为字典形式,取出prepay_id用于再次签名

    # 二进制的xml转换为字典
    def xml_to_dict(self,data):
        import xml.etree.ElementTree as ET
        xml_dict = {}
        # 将二进制xml数据转换成了xml老母猪
        data_dic = ET.fromstring(data)  # 大概是个xml老母猪 <Element 'xml' at 0x0000000002168EF8>
        # 遍历老母猪,生成真正的字典,key为标签名,val为标签值

        for item in data_dic:
            xml_dict[item.tag] = item.text  # <xml> <appid>123456</appid> </xml>
        return xml_dict  # {'appid': '123456'}

    def pay(self):
        ...
                # xml发送二进制,返回的肯定也是二进制,requests模块返回的二进制数据在response.content里面
        res_data=self.xml_to_dict(response.content)  # 我们将接受到的二进制转换为字典
        prepay_id = res_data["prepay_id"]
        # 至此统一下单流程完成++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
View Code

二、再次签名

收到统一下单中得到的prepay_id,我们就可以根据API再次签名流程和数据要求来准备数据,再次根据签名算法签名

    def pay(self):
        ......
        # 下面开始进入再次签名流程*******************************************************************
        # 调起再次签名函数,得到再次签名的数据返回给前端
        data=self.two_sign(prepay_id)
        return  data

    # 再次签名 文档中有参数说明
    def two_sign(self,prepay_id):
        timeStamp=str(int(time.time()))
        nonceStr=self.get_str() # 这个和统一下单中生成方法一样,直接使用
        data_dict={
            "appId":settings.AppId,
            "timeStamp":timeStamp,
            "nonceStr":nonceStr,
            "package":f"prepay_id={prepay_id}",  # 文档中有说明,就是统一下单返回值中的prepay_id
            "signType":"MD5"
        }
        # 按照官网只是一步一步来,和统一下单中sign步骤一样
        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().upper()
        # 看文档,前端需要data_dict中四个参数和这个签名
        data_dict["paySign"]=sign
        data_dict.pop("appId") # 这个肯定不能给前端
        return  data_dict # 到这里再次签名流程后端结束,开始去写前端*************************    
View Code

将再次签名的数据data发送给前端,小程序开始调用wx.requestPayment来调起支付

 

 

 三 、完整代码

前台代码

  pay:function(){
    wx.request({
      url: "http://127.0.0.1:8000/pay/",
      method: "POST",
      data:{"login_key":wx.getStorageSync("login_key")},
      header: { "content-type": "application/json" },
      success: function (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)
           },
        
        })
      }
    })

  },
test.js

view/Order 订单接口 

from rest_framework.views import  APIView
from  rest_framework.response import  Response
from app01.wx import wx_login
from django.core.cache import cache
import hashlib,time
import random
from app01.wx import settings
import requests
class Pay(APIView):
    def post(self,request): # 第一步:用login_key先取出openid再说别的
        param=request.data
        if param.get("login_key"):
            openid,session_key=cache.get(param.get("login_key")).split("&")
            self.openid=openid  # 将其制作成属性

            # 获取ip  这一块是写后面data的时候发现需要ip来这里补充
                #如果是Nginx做的负载均衡,就是Nginx来访问服务器,不能直接获取IP,就要HTTP_X_FORWARDED_FOR
            if request.META.get('HTTP_X_FORWARDED_FOR'):
                self.ip =request.META['HTTP_X_FORWARDED_FOR']
            else:
                #如果没有用Nginx做负载均衡,就是用户直接访问,直接用REMOTE_ADDR 获取自身ip
                self.ip = request.META['REMOTE_ADDR']

            data = self.pay()  # 第二步 :获取各种必需的参数,官网统一订单有说明

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

    # nonce_str:随机字符串,长度要求在32位以内
    def get_str(self):
        str_all="1234567890abcdefghjklmasdwery"
        nonce_str="".join(random.sample(str_all,20))
        return nonce_str

    # 唯一订单号
    def get_order(self):
        # 这里只是示意,小公司用时间戳加随机就差不多了保证唯一了
        # 大公司高并发,可以用redis生成全局唯一id再做拼接
        order_id=str(time.strftime("%Y%m%d%H%M%S"))
        return order_id

    # 二进制的xml转换为字典
    def xml_to_dict(self,data):
        import xml.etree.ElementTree as ET
        xml_dict = {}
        # 将二进制xml数据转换成了xml老母猪
        data_dic = ET.fromstring(data)  # 大概是个xml老母猪 <Element 'xml' at 0x0000000002168EF8>
        # 遍历老母猪,生成真正的字典,key为标签名,val为标签值

        for item in data_dic:
            xml_dict[item.tag] = item.text  # <xml> <appid>123456</appid> </xml>
        return xml_dict  # {'appid': '123456'}

    # 根据官网步骤一步一步来
    def get_sign(self):
        data_dic = {
            "nonce_str": self.nonce_str,
            "out_trade_no": self.out_trade_no,
            "spbill_create_ip": self.ip,
            "notify_url": self.notify_url,
            "openid": self.openid,
            "body": self.body,
            "trade_type": "JSAPI",
            "appid": self.appid,
            "total_fee": self.total_fee,
            "mch_id": self.mch_id
        }
        # f为参数替换
        sign_str = "&".join([f"{k}={data_dic[k]}" for k in sorted(data_dic)])
        sign_str = f"{sign_str}&key={settings.pay_apikey}"
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        return md5.hexdigest().upper()


    def pay(self):  # 第二步;根据官方文档中各个参数,我们先组织这些参数,上面的方法都是得到函数
        self.appid=settings.AppId
        self.mch_id=settings.pay_mchid
        self.nonce_str=self.get_str() # 随机字符串,长度要求在32位以内
        self.body="饼哥生活费" # 商品简单描述,该字段请按照规范传递
        self.out_trade_no=self.get_order() # 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
        self.total_fee=1 # 订单总金额,单位为分
        self.spbill_create_ip=self.ip # 调用微信支付API的机器IP(在上面主函数post里面获取)
        self.notify_url="http://www.baidu.com" #异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
        self.trade_type="JSAPI" # 小程序交易类型,固定这样
        self.sign = self.get_sign() # 通过签名算法计算得出的签名值,详见官方文档签名生成算法

       # 下面开始生成data,f为参数替换,这一大堆就是data
        data=f"""  
        <xml>
               <appid>{self.appid}</appid>
               <body>{ self.body}</body>
               <mch_id>{self.mch_id}</mch_id>
               <nonce_str>{self.nonce_str}</nonce_str>
               <notify_url>{self.notify_url}</notify_url>
               <openid>{self.openid}</openid>
               <out_trade_no>{self.out_trade_no}</out_trade_no>
               <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip>
               <total_fee>{self.total_fee}</total_fee>
               <trade_type>{self.trade_type}</trade_type>
               <sign>{self.sign}</sign>
        </xml>
        """
        # 第三步:上面将所有需要的参数都准备齐全了,下面开始请求url发送数据,发送xml数据肯定是post请求,并且要发二进制
        url="https://api.mch.weixin.qq.com/pay/unifiedorder"  # 固定请求url
        response=requests.post(url,data.encode("utf-8"),headers={"content-type":"application/xml"})

        # 第四步 :接收并处理请求返回值
        # xml发送二进制,返回的肯定也是二进制,requests模块返回的二进制数据在response.content里面
        res_data=self.xml_to_dict(response.content)  # 我们将接受到的二进制转换为字典
        prepay_id = res_data["prepay_id"]
        # 至此统一下单流程完成++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        # 下面开始进入再次签名流程*******************************************************************
        # 调起再次签名函数,得到再次签名的数据返回给前端
        data=self.two_sign(prepay_id)
        return  data

    # 再次签名 文档中有参数说明
    def two_sign(self,prepay_id):
        timeStamp=str(int(time.time()))
        nonceStr=self.get_str() # 这个和统一下单中生成方法一样,直接使用
        data_dict={
            "appId":settings.AppId,
            "timeStamp":timeStamp,
            "nonceStr":nonceStr,
            "package":f"prepay_id={prepay_id}",  # 文档中有说明,就是统一下单返回值中的prepay_id
            "signType":"MD5"
        }
        # 按照官网只是一步一步来,和统一下单中sign步骤一样
        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().upper()
        # 看文档,前端需要data_dict中四个参数和这个签名
        data_dict["paySign"]=sign
        data_dict.pop("appId") # 这个肯定不能给前端
        return  data_dict # 到这里再次签名流程后端结束,开始去写前端*************************

 




免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM