基于走了一段弯路,东拼西凑实现的功能,想借此记录一下实现过程。
这里还特别注意官方demo中基于python实现的还比较少,要么就是一堆代码库感觉特别复杂,这里想用最简洁的方式实现获取二维码返回至前端。
前期需要准备的商户参数:
# 微信支付商户号
MCH_ID = ""
# API v2密钥
API_KEY = ""
# APPID
APP_ID = ""
接下来定义的是一些全局变量:
# 微信统一下单api
UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"
# 终端IP
CREATE_IP = "127.0.0.1"
# 支付完成回调地址
NOTIFY_URL = ""
准备完以上的内容后,下面就可以实现具体的调用支付代码了
生成请求参数
- 首先是在python中对请求API要用到的参数进行封装,这里我尽量只填写需要用到的必填项参数。其余可选参数我都进行了省略,大家可以参考微信支付官方文档按照自己的需要进行填写
from collections import OrderedDict def createParameters(desc, amount, out_trade_no, attach): jsApiObj = OrderedDict() jsApiObj["appid"] = APP_ID jsApiObj["attach"] = attach jsApiObj["body"] = desc jsApiObj["device_info"] = "WEB" jsApiObj["mch_id"] = MCH_ID jsApiObj["nonce_str"] = createNoncestr() jsApiObj["notify_url"] = NOTIFY_URL jsApiObj["out_trade_no"] = out_trade_no jsApiObj["spbill_create_ip"] = CREATE_IP jsApiObj["total_fee"] = amount jsApiObj["trade_type"] = "NATIVE" jsApiObj["sign"] = getSign(jsApiObj) return jsApiObj
注意上面这里的参数顺序需要按照ASCII码从小到大排序,这里我省略了排序代码,直接手动进行排序,并使用OrderedDict有序字典来保持顺序。如果大家不想调用排序代码,直接和上面的顺序保持一致即可。
其中的attach参数为用于透传的附加参数,在微信支付完成后的回调接口中会原封不动的传回来,由我们自己的接口进行接收,后面的代码会演示。
desc是商品描述,out_trade_no则是我们自己后台系统生成的订单号,amount为支付金额。要注意这里的金额类型只能为正整数,单位为分。
- 接下来nonce_str是我们自己随机生成的一个32位字符串,可参考如下函数:
import random
def createNoncestr(length=32):
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
strs = []
for x in range(length):
strs.append(chars[random.randrange(0, len(chars))])
return "".join(strs)
- 最后要说的就是sign这个参数了,这是先将上面所有的参数填写后,也就是整个字典对象进行签名,将签名后的值传入。具体的getSign函数如下:
import hashlib
def getSign(obj):
stringA = ""
for item in obj.items():
key, value = item
stringA += "{0}={1}&".format(key, value)
stringA += "key=" + API_KEY
sign = hashlib.md5(stringA.encode()).hexdigest()
return sign.upper()
封装xml数据
在上面的代码中,我们通过createParameter函数将会获得一个json格式的请求参数。但是由于微信支付接口的接收和响应都为xml数据格式,因此我们还需要将json字典数据转化为xml格式,函数如下:
def jsonToXml(obj):
xml = ["<xml>"]
for k, v in obj.items():
if str(v).isdigit():
xml.append("<{0}>{1}</{0}>".format(k, v))
else:
xml.append("<{0}><![CDATA[{1}]]></{0}>".format(k, v))
xml.append("</xml>")
return "".join(xml)
请求微信统一下单API
将上面准备好的参数在python中向接口发起请求
import requests
parameters = createParameters(desc, amount, out_trade_no, attach)
body = jsonToXml(parameters)
resp = requests.post(UFDODER_URL, data=body.encode("utf-8"))
对返回参数进行解析
执行以上代码后,如果请求成功,微信接口将会返回类似如下数据:
<xml>
<return_code>
<![CDATA[SUCCESS]]>
</return_code>
<return_msg>
<![CDATA[OK]]>
</return_msg>
<result_code>
<![CDATA[SUCCESS]]>
</result_code>
<mch_id>
<![CDATA[**********]]>
</mch_id>
<appid>
<![CDATA[wx74febd3d3975ef65]]>
</appid>
<device_info>
<![CDATA[WEB]]>
</device_info>
<nonce_str>
<![CDATA[HSVJfRl6OAgYbLKE]]>
</nonce_str>
<sign>
<![CDATA[D1B58BD8EEC2A820FA378ADB058B237E]]>
</sign>
<prepay_id>
<![CDATA[wx2414513178394979646babfc9c4fdb0000]]>
</prepay_id>
<trade_type>
<![CDATA[NATIVE]]>
</trade_type>
<code_url>
<![CDATA[weixin://wxpay/bizpayurl?pr=gVBMkSYzz]]>
</code_url>
</xml>
当return_code为SUCCESS时,便可以在code_url提取到支付二维码链接,接下来我们将返回结果封装一个XMLHandler类解析成字典:
import xml.sax.handler
class XMLHandler(xml.sax.handler.ContentHandler):
def __init__(self):
self.buffer = ""
self.mapping = {}
def startElement(self, name, attributes):
self.buffer = ""
def characters(self, data):
self.buffer += data
def endElement(self, name):
self.mapping[name] = self.buffer
def getDict(self):
return self.mapping
def xmlParse(data):
xh = XMLHandler()
xml.sax.parseString(data, xh)
ret = xh.getDict()
return ret
流程整合
整合上面所有的步骤后,代码如下:
import requests
import hashlib
import random
from collections import OrderedDict
from utils.wechatUtils import xmlParser
from .settings import APP_ID, MCH_ID, API_KEY, CREATE_IP, \
NOTIFY_URL, UFDODER_URL
def createNoncestr(length=32):
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
strs = []
for x in range(length):
strs.append(chars[random.randrange(0, len(chars))])
return "".join(strs)
def getSign(obj):
stringA = ""
for item in obj.items():
key, value = item
stringA += "{0}={1}&".format(key, value)
stringA += "key=" + API_KEY
sign = hashlib.md5(stringA.encode()).hexdigest()
return sign.upper()
def jsonToXml(obj):
xml = ["<xml>"]
for k, v in obj.items():
if str(v).isdigit():
xml.append("<{0}>{1}</{0}>".format(k, v))
else:
xml.append("<{0}><![CDATA[{1}]]></{0}>".format(k, v))
xml.append("</xml>")
return "".join(xml)
def createParameters(desc, amount, out_trade_no, attach):
jsApiObj = OrderedDict()
jsApiObj["appid"] = APP_ID
jsApiObj["attach"] = attach
jsApiObj["body"] = desc
jsApiObj["device_info"] = "WEB"
jsApiObj["mch_id"] = MCH_ID
jsApiObj["nonce_str"] = createNoncestr()
jsApiObj["notify_url"] = NOTIFY_URL
jsApiObj["out_trade_no"] = out_trade_no
jsApiObj["spbill_create_ip"] = CREATE_IP
jsApiObj["total_fee"] = amount
jsApiObj["trade_type"] = "NATIVE"
jsApiObj["sign"] = getSign(jsApiObj)
return jsApiObj
def request_pay_unifiedorder(desc, amount, out_trade_no, attach):
parameters = createParameters(desc, amount, out_trade_no, attach)
body = jsonToXml(parameters)
resp = requests.post(UFDODER_URL, data=body.encode("utf-8"))
return xmlParser.xmlParse(resp.content)
最后再附上code_url参数解析成base64图片的代码:
import io
import base64
import qrcode
def make_qrcode(text):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10, border=4,
)
qr.add_data(text)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
return img
def generate_base64(text):
img = make_qrcode(text)
buffered = io.BytesIO()
img.save(buffered, format="PNG")
img_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
return img_base64
整合调用
ret = request_pay_unifiedorder(desc, amount, no, json.dumps({
"amount": amount,
"quantity": quantity
}))
qrcode = generate_base64(ret["code_url"])
支付完成接收回调
扫码支付完成后,微信会将支付结果请求至我们之前配置好的回调接口。我们需要将接收到的参数进行解析,并读取上面提到过的透传内容:
class PayNotify(APIView):
authentication_classes = []
schema = None
def get(self, request):
return self.post(request)
def post(self, request):
if request.body != "":
xmlDict = xmlParse(request.body)
print("支付回调:")
print(request.body)
return_data = {
"return_code": "FAIL",
"return_msg": "OK"
}
if xmlDict["return_code"] == "SUCCESS":
out_trade_no = xmlDict["out_trade_no"]
transaction_id = xmlDict["transaction_id"]
attach = xmlDict["attach"]
return_data["return_code"] = "SUCCESS"
return HttpResponse(trans_dict_to_xml(return_data), content_type="application/xml")
其中xmlParse与trans_dict_to_xml函数如下:
import xml.sax.handler
def xmlParse(data):
xh = XMLHandler()
xml.sax.parseString(data, xh)
ret = xh.getDict()
return ret
def trans_dict_to_xml(data_dict):
data_xml = []
for k in sorted(data_dict.keys()):
v = data_dict.get(k)
data_xml.append('<{key}><![CDATA[{value}]]></{key}>'.format(key=k, value=v))
return '<xml>{}</xml>'.format(''.join(data_xml)).encode('utf-8')