目錄
一、小程序支付官方文檔
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)
},
})
}
})
}