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