python实现微信h5支付(可以在微信默认浏览器支付的一种方式)


python实现微信h5支付

1.首先微信h5支付的含义:

①手机浏览器访问

②非微信浏览器访问

如果是微信浏览器访问会抛异常

常见错误见官方文档

https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4


官方文档:
https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4

业务流程:

商户系统和微信支付系统主要交互说明:

#步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。
用户在app页面选择商品,确定数量,提交订单,提供必要的参数
app支付需要的前端参数
1. body  商品描述
2. total_fee 商品价格

#步骤2: 商户后台生成本地订单
1.生成订单号,保证唯一性
order_sn = "%s%s%s" % (request.user.id, datetime.datetime.now().strftime('%Y%m%d%H%M%S'),
                                   random.randint(1, 99))
2.根据订单号,生成本地订单(生成订单对象,订单状态肯定是未支付,支付成功的回调时再改为支付成功状态)
def generate_order(user, post_data, order_sn, nickname, openid):
    good_kind = post_data.get("good_kind")
    good_id = post_data.get('good_id')
    paid_amount = post_data.get('paid_amount', 0)
    try:
        good_kind = int(good_kind)
        good_id = int(good_id)
        paid_amount = float(paid_amount)
        #1 课程  2 会员卡
        if good_kind == 1:
            course = Course.objects.get(pk=good_id)
            record = Record.objects.create(
                user=user, course=course, telephone=user.phone, center=get_center_for_appid(),
                price=paid_amount, order_sn=order_sn, nickname=nickname, openid=openid)
            if not paid_amount:
                record.status = 2
                record.save()
        elif good_kind == 2:
            card = Vip.objects.get(pk=good_id)
            record = Record.objects.create(
                user=user, card=card, price=paid_amount, center=get_center_for_appid(),
                order_sn=order_sn, nickname=nickname, telephone=user.phone, openid=openid)
        else:
            record = None
    except Exception as e:
        record = None
    return record

#步骤3:由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
使用pip install weixin-python==0.5.4(模块)
1.导入from weixin import Weixin
2.初始化(https://www.cnblogs.com/gjh99/p/10536905.html)
we_chat = Weixin(dict(
    #app_id 微信开放平台审核通过的应用APPID(请登录open.weixin.qq.com查看,注意与公众号的APPID不同)
    WEIXIN_APP_ID=WECHAT_APP_ID,
    #mch_id 微信支付分配的商户号
    WEIXIN_MCH_ID=WECHAT_MCH_ID,
    #mch_key 微信支付密钥
    WEIXIN_MCH_KEY=WECHAT_MCH_KEY,
    #回调url
    WEIXIN_NOTIFY_URL=WECHAT_NOTIFY_URL,
    #公钥文件
    WEIXIN_MCH_KEY_FILE='',
    #私钥文件
    WEIXIN_MCH_CERT_FILE=''
))


3.因为原模块封装好的方法jsapi没有将mweb_url放入签名,故重写了h5_jsapi方法,将mweb_url字段加入签名
def h5_jsapi(we_chat, **kwargs):
    kwargs.setdefault("trade_type", "JSAPI")
    raw = we_chat.unified_order(**kwargs)
    package = "prepay_id={0}".format(raw["prepay_id"])
    mweb_url = raw["mweb_url"]
    timestamp = str(int(time.time()))
    nonce_str = we_chat.nonce_str
    raw = dict(appId=we_chat.app_id, timeStamp=timestamp, mweb_url=mweb_url,
               nonceStr=nonce_str, package=package, signType="MD5")
    sign = we_chat.sign(raw)
    return dict(package=package, appId=we_chat.app_id, signType="MD5",
                timeStamp=timestamp, nonceStr=nonce_str, mweb_url=mweb_url, sign=sign)


spbill_create_ip = request.META.get("REMOTE_ADDR")
jsdict = h5_jsapi(
    we_chat, 
    #订单号
    out_trade_no=order_sn,
    #商品描述
    body=body,
    #支付价格,以分为单位
    total_fee=int(float(total_fee) * 100),
    #交易类型
    trade_type='MWEB',
    #用户ip(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_5)
    spbill_create_ip=spbill_create_ip
)


4.接收微信后台返回的数据字典jsdict,需要返回给前台,后台再写个回调函数就ok
{
    "status": 1,
    "data": {
        "mweb_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?			prepay_id=wx11165744343104a03ec402871636571500&package=3494349132",
        "appId": "你的appid",
        "package": "prepay_id=wx11165744343104a03ec402871636571500",
        "timeStamp": "1576054664",
        "nonceStr": "b7t5brZBvvClNsAZtN9jyVPpawhiLCqP",
        "signType": "MD5",
        "sign": "9DBF2BE500204B0B686A1EEB8C7AA82A"
    }
}

5.回调函数
@api_view(['POST'])
@permission_classes([AllowAny])
def wechat_callback(request):
    if request.method == 'POST':
        try:
            data = we_chat.to_dict(request.body)
            order_sn = data.get('out_trade_no', '')
            # 支付结果,用户正常支付,会返回SUCCESS
            result_code = data.get('result_code', '')
            # check 检查微信回传数据是否可靠
            if not we_chat.check(data):
                return HttpResponse(we_chat.reply("签名验证失败", False))
            if result_code == 'SUCCESS':
                return HttpResponse(we_chat.reply("OK", True), content_type='text/xml')
        except Exception as e:
            return HttpResponse(we_chat.reply("签名验证失败", False), content_type='text/xml')

        
#后台代码
   @list_route(methods=["POST"])
    def h5_pay(self, request, *args, **kwargs):
        post_data = request.DATA
        body = post_data.get('body', '微信支付测试')
        total_fee = post_data.get('total_fee', 0.01)
        if not total_fee:
            return Response({"status": 0, "errormsg": "缺少参数"})
        # 订单号生成规则:用户id+当前时间+随机数
        order_sn = "%s%s%s" % (request.user.id, datetime.datetime.now().strftime('%Y%m%d%H%M%S'),
                               random.randint(1, 99))
        # 生成本地订单
        order = generate_order()
        spbill_create_ip = request.META.get("REMOTE_ADDR")
        jsdict = h5_jsapi(we_chat, out_trade_no=order_sn, body=body, total_fee=int(float(total_fee) * 100),
                          trade_type='MWEB', spbill_create_ip=spbill_create_ip)
        return Response({"status": 1, "data": jsdict})

以上是微信h5支付,实际应用时,用户一般会使用微信分享h5,其他微信用户点开时基本是默认微信浏览器打开h5导致抛异常

所以判断用户是否使用微信浏览器打开h5页面,如果不是就走h5支付,如果是的话,此处我的解决方法是让用户走JSAPI支付,

判断浏览器是否是微信浏览器

https://www.cnblogs.com/sherryweb/p/11239127.html

    function is_weixin_function() {
        var ua = window.navigator.userAgent.toLowerCase();
        if (ua.match(/MicroMessenger/i) == 'micromessenger') {
            return 1
        } else {
            //不是微信浏览器
            return 0
        }
    }

JSAPI支付

 #1.通过前端返回的code获取openid,使用jsapi支付方式时,openid是必传参数
   #通过werobot模块获取openid
   import werobot(python2 -m pip install werobot==1.2.0)
	
   def jsapi_pay(self, request, *args, **kwargs):
        code = request.DATA.get("code")
        robot = werobot.WeRoBot(token=settings.WECHAT_TOKEN)
        robot.config["APP_ID"] = settings.WECHAT_APP_ID
        robot.config["APP_SECRET"] = settings.WECHAT_APP_SECRET
        client = robot.client
        params = {
            'appid': robot.config["APP_ID"],
            'secret': robot.config["APP_SECRET"],
            'code': code,
            'grant_type': 'authorization_code',
        }
        r_json = client.request('GET', 'https://api.weixin.qq.com/sns/oauth2/access_token',
                                    params=params, timeout=10)
        openid = r_json.get('openid')
        
        
        
#2.生成本地订单

#3.调用统一下单接口
jsdict = we_chat.jsapi(
    out_trade_no=order_sn,
    body=body,
    total_fee=total_fee,
    trade_type='JSAPI', 
    openid=openid
)

#4.返回的jsdict给前端,后台就ok了
{
    "status": 1,
    "data": {
        "appId": "你的appid",
        "package": "prepay_id=wx11165744343104a03ec402871636571500",
        "timeStamp": "1576054664",
        "nonceStr": "b7t5brZBvvClNsAZtN9jyVPpawhiLCqP",
        "signType": "MD5",
        "sign": "9DBF2BE500204B0B686A1EEB8C7AA82A"
    }
}

#后台代码
     @list_route(methods=["POST"])
    def jsapi_pay(self, request, *args, **kwargs):
        #这里的appid是公众号的哦
        wei_xin = Weixin(dict(WEIXIN_APP_ID=settings.WECHAT_APP_ID, WEIXIN_MCH_ID=settings.WEIXIN_MCH_ID,
                              WEIXIN_MCH_KEY=settings.WEIXIN_MCH_KEY,
                              WEIXIN_NOTIFY_URL=settings.WECHAT_NOTIFY_URL, WEIXIN_MCH_KEY_FILE='', WEIXIN_MCH_CERT_FILE=''))
        post_data = request.DATA
        code = post_data.get("code")
        redis_cli = get_redis_cli("default")
        if redis_cli.keys(code):
            return Response({"status": 0, "errormsg": "code已使用"})
        else:
            redis_cli.set(code, "", ex=24*60*60)
        body = post_data.get('body', '微信支付测试')
        try:
            total_fee = int(float(post_data.get('total_fee'))*100)
        except TypeError:
            return Response({"status": 0, "errormsg": "缺少支付价格或价格格式错误"})
        robot = werobot.WeRoBot(token=settings.WECHAT_TOKEN)
        robot.config["APP_ID"] = settings.WECHAT_APP_ID
        robot.config["APP_SECRET"] = settings.WECHAT_APP_SECRET
        client = robot.client
        params = {
            'appid': robot.config["APP_ID"],
            'secret': robot.config["APP_SECRET"],
            'code': code,
            'grant_type': 'authorization_code',
        }
        r_json = client.request('GET', 'https://api.weixin.qq.com/sns/oauth2/access_token',
                                    params=params, timeout=10)
        openid = r_json.get('openid')
        print(code, openid)
        if not openid or not total_fee:
            return Response({"status": 0, "errormsg": "缺少参数"})
        # 订单号生成规则:用户id+当前时间+随机数
        order_sn = "%s%s%s" % (request.user.id, datetime.datetime.now().strftime('%Y%m%d%H%M%S'),
                               random.randint(1, 99))
        # 生成本地订单
        order = generate_order()
        if not order:
            return Response({"status": 0, "errormsg": "创建本地订单失败"})
        jsdict = wei_xin.jsapi(out_trade_no=order_sn, body=body, total_fee=total_fee, trade_type='JSAPI', openid=openid)
        return Response({"status": 1, "data": jsdict})

这里遇到了问题,苹果可以正常支付,安卓不能正常支付,前端发起支付代码如下

     function onBridgeReady() {
                                    WeixinJSBridge.invoke(
                                        'getBrandWCPayRequest', {
                                            "appId": appId,     //公众号名称,由商户传入
                                            "timeStamp": timeStamp,         //时间戳,自1970年以来的秒数
                                            "nonceStr": nonceStr, //随机串
                                            "package": package_1,
                                            "signType": signType,         //微信签名方式:
                                            "paySign": paySign //微信签名
                                        },
                                        function (res) {
                                            //writeObj(res)
                                            if (res.err_msg == "get_brand_wcpay_request:ok") {
                                                // 使用以上方式判断前端返回,微信团队郑重提示:
                                                //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                                                alert('支付成功')
                                            } else if (res.err_msg == 'get_brand_wcpay_request:fail') {
                                                alert('支付失败')
                                                window.location.href = '/pay/order'
                                            } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
                                                alert('支付取消')
                                                window.location.href = '/pay/order'
                                            }
                                        });
                                }

                                if (typeof WeixinJSBridge == "undefined") {
                                    if (document.addEventListener) {
                                        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                                    }
                                } else {
                                    onBridgeReady();
                                }
                            }
                        }
                    }

百度下,说推荐使用 wx.chooseWXPay,结果安卓可以了,苹果又挂了,无语,只好判断安卓,苹果分开用了

    $('.js-order-pay').click(function () {
        var _msg = $(this).attr('data-message')
        var auth = 'JWT ' + localStorage.getItem('token')
        if (_msg == '1') {
            console.log(1111111111)
            //支付宝
            var formData = {
                total_fee: 0.01,
            }
            $.ajax({
                type: "POST",
                url: '/api/pay/ali_pay/h5_pay/',
                data: JSON.stringify(formData),
                dataType: "json",
                contentType: "application/json",
                processData: false,
                headers: {"X-CSRFToken": getCookie("csrftoken")},
                beforeSend: function (xhr) {
                    xhr.setRequestHeader("Authorization", auth);
                },
                success: function (data) {
                    if (data.status == 1) {
                        var div = document.createElement('div');
                        div.innerHTML = data.data.form
                        document.body.appendChild(div);
                        var subForm = document.getElementsByTagName("form")[0];
                        subForm.submit();
                    } else {
                        alert(data.errormsg)
                    }
                },
            })
        } else {
            //微信
            var _is_weixin = is_weixin_function()
            if (_is_weixin == 1) {
                //微信浏览器
                var formData = {
                    total_fee: 0.01,
                    code: _code
                }
                $.ajax({
                    type: "POST",
                    url: '/api/pay/wechat_pay/jsapi_pay/',
                    data: JSON.stringify(formData),
                    dataType: "json",
                    contentType: "application/json",
                    processData: false,
                    headers: {"X-CSRFToken": getCookie("csrftoken")},
                    beforeSend: function (xhr) {
                        xhr.setRequestHeader("Authorization", auth);
                    },
                    success: function (data) {
                        if (data.status == 1) {
                            var appId = data.data.appId
                            var timeStamp = data.data.timeStamp
                            var nonceStr = data.data.nonceStr
                            var package_1 = data.data.package
                            var signType = data.data.signType
                            var paySign = data.data.sign
                            if (isAndroid) {
                                wx.config({
                                    debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                                    appId: appId, // 必填,公众号的唯一标识
                                    timestamp: timeStamp, // 必填,生成签名的时间戳
                                    nonceStr: nonceStr, // 必填,生成签名的随机串
                                    signature: paySign,// 必填,签名,见附录1
                                    jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2,如果只是支付,只用这一个参数就够了
                                });
                                wx.ready(function () {
                                    wx.chooseWXPay({
                                        appId: appId,
                                        timestamp: timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
                                        nonceStr: nonceStr, // 支付签名随机串,不长于 32 位
                                        package: package_1, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
                                        signType: signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
                                        paySign: paySign, // 支付签名
                                        success: function (res) {
                                            // 支付成功后的回调函数
                                        },
                                        //如果你按照正常的jQuery逻辑,下面如果发送错误,一定是error,那你就太天真了,当然,jssdk文档中也有提到
                                        fail: function (res) {
                                            //接口调用失败时执行的回调函数。
                                            window.location.reload()
                                        },
                                        complete: function (res) {
                                            //接口调用完成时执行的回调函数,无论成功或失败都会执行。
                                        },
                                        cancel: function (res) {
                                            //用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
                                            //writeObj(res)
                                            if (res.errMsg == 'chooseWXPay:cancel') {
                                                window.location.href = '/pay/order'
                                            }
                                        },
                                        trigger: function (res) {
                                            //监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。
                                        }
                                    });
                                });
                            }
                            if (isiOS) {
                                function onBridgeReady() {
                                    WeixinJSBridge.invoke(
                                        'getBrandWCPayRequest', {
                                            "appId": appId,     //公众号名称,由商户传入
                                            "timeStamp": timeStamp,         //时间戳,自1970年以来的秒数
                                            "nonceStr": nonceStr, //随机串
                                            "package": package_1,
                                            "signType": signType,         //微信签名方式:
                                            "paySign": paySign //微信签名
                                        },
                                        function (res) {
                                            //writeObj(res)
                                            if (res.err_msg == "get_brand_wcpay_request:ok") {
                                                // 使用以上方式判断前端返回,微信团队郑重提示:
                                                //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                                                alert('支付成功')
                                            } else if (res.err_msg == 'get_brand_wcpay_request:fail') {
                                                alert('支付失败')
                                                window.location.href = '/pay/order'
                                            } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
                                                alert('支付取消')
                                                window.location.href = '/pay/order'
                                            }
                                        });
                                }

                                if (typeof WeixinJSBridge == "undefined") {
                                    if (document.addEventListener) {
                                        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                                    }
                                } else {
                                    onBridgeReady();
                                }
                            }
                        }
                    }


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM