Django实现网页微信扫码支付

        基于走了一段弯路,东拼西凑实现的功能,想借此记录一下实现过程。

        这里还特别注意官方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')

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jaris_w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值