小程序支付
一、小程序支付官方文档
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)
},
})
}
})
}
选择了IT,必定终身学习