分享

Django整合微信h5支付

 看见就非常 2020-04-29

    终于在踩完了无数坑之后,发现这个流程必须记录下来。在这之前,本人不仅尝试过网上各种各样的帖子,也参考了微信支付的官方文档,都最后都是发现:要么难以理解,要么甚至按照微信支付的官方文档也会遇到问题。

    虽然如果完全参考官方的文档会被误导,但是其中一部分还是可以参考的。

    首先来看要准备的环境:

    1.认证的微信公众号,并且开通了微信支付

    2.开通商户平台账号

    3.带域名的服务器

    以上三点是对接微信h5支付的前提条件,接下来我们可以参考微信支付的官方文档:由于是h5页面调用的微信支付,所以这里点击链接后选择JSAPI支付。在统一下单的列表中,微信给出了需要请求的API以及参数。由于我参考的其他的技术贴自己请求微信的API一直没成功,所以最后我还是直接调用了微信官方对支付封装好的代码,代码可以在github上下载。下载好之后,需要分别将weixin文件夹中的config.py、lib.py和pay.py脚本文件拷贝到我们的工程目录下。拷贝完成后对这些脚本文件稍作配置:

    config.py脚本中需要配置的都是些微信公众号的信息,像APPID、APPSECRET等,其中都有注释。登录到微信公众平台,在基本配置中可以看到一下信息:

    

    这里就有APPID、APPSECRET和TOKEN,服务器地址要配置成服务器上的一个接口(服务器带上域名),在提交配置的时候微信会对我们配置的这个接口发送一个请求,接口需要接收微信传来的参数以及返回正确的响应才能配置成功。服务器上的接口如下:

  1. class WXRest(APIView):
  2. authentication_classes = []
  3. permission_classes = []
  4. def get(self, request):
  5. signature = request.GET.get('signature')
  6. timestamp = request.GET.get('timestamp')
  7. nonce = request.GET.get('nonce')
  8. echostr = request.GET.get('echostr')
  9. wechat_instance = WechatBasic(conf=wxConf)
  10. if not wechat_instance.check_signature(signature=signature, timestamp=timestamp, nonce=nonce):
  11. return HttpResponseBadRequest('Verify Failed')
  12. else:
  13. return HttpResponse(echostr, content_type="application/json")

    wxConf的配置如下:

  1. wxConf = WechatConf(
  2. token='xxxxxx',
  3. appid=SOCIAL_AUTH_WEIXIN_APPID,
  4. appsecret=SOCIAL_AUTH_WEIXIN_SECRET,
  5. encrypt_mode='normal',
  6. encoding_aes_key='xxxxxx'
  7. )

    其中token需要填写之前公众号开发信息中的令牌(Token),appid就是公众号中的开发者ID,appsecret为开发者密码,encoding_aes_key为服务器配置项中的服务器加解密密钥。

    我这里使用的drf的API View,注意其中的

  1. authentication_classes = []
  2. permission_classes = []

    这两句是取消该接口的token认证和登录认证,否则如果接口上有任何的权限认证,微信的请求都是不成功的。

    config.py脚本中剩下的商户ID MCHID和商户支付密钥KEY则需要登录到微信商户平台中获取。

    做好这些配置之后,就可以正式开始View的编写了:

  1. from utils.wechatUtils.pay import JsApi_pub, UnifiedOrder_pub
  2. class WxPayConfig(APIView):
  3. def post(self, request):
  4. money = request.data[u"number"]
  5. admin_user = AdminUser.objects.filter(username=request.user.username)[0]
  6. master = Master.objects.filter(admin_user=admin_user)[0]
  7. socialAccounts = SocialAccounts.objects.filter(admin_user=admin_user)[0]
  8. openid = socialAccounts.openid
  9. money = int(float(money)*100)
  10. out_trade_no = genOrder(master.phone)
  11. jsApi = JsApi_pub()
  12. unifiedOrder = UnifiedOrder_pub()
  13. unifiedOrder.setParameter("openid", openid)
  14. unifiedOrder.setParameter("body", "储值卡充值")
  15. unifiedOrder.setParameter("out_trade_no", out_trade_no)
  16. unifiedOrder.setParameter("total_fee", str(money))
  17. unifiedOrder.setParameter("notify_url", NOTIFY_URL)
  18. unifiedOrder.setParameter("trade_type", "JSAPI")
  19. prepay_id = unifiedOrder.getPrepayId()
  20. jsApi.setPrepayId(prepay_id)
  21. jsApiParameters = jsApi.getParameters()
  22. conn = redis.StrictRedis()
  23. conn.set("out_trade_no_" + out_trade_no, json.dumps({"type": "储值卡充值", "admin_user": admin_user.id},
  24. ensure_ascii=False), 60 * 10)
  25. return HttpResponse(json.dumps(json.loads(jsApiParameters)), content_type="application/json")

      这里同样是使用drf的APIView,number参数是从前端提交的支付金额,从微信提供的pay.py中导入JsApi_pub和UnifiedOrder_pub,然后设置所需的参数。其中参数openid就是用户微信的openid,body是商品名称,out_trade_no为我们自己服务器生成的订单号,total_fee就是付款金额,notify_url为微信支付成功后回调的我们服务器的URL,trade_type参数为JSAPI表示支付类型为h5页面支付。填写完这些参数后,调用UnifiedOrder_pub类中的getPrepayId函数获得prepay_id,并进行设置。最后将调用jsApi.getParameters()函数返回的结果返回到前端。

      生成参数out_trade_no的函数如下:

  1. def genOrder(phone="176******"):
  2. id_number = str(phone) + str(time.time())
  3. resId = str(uuid.uuid3(uuid.NAMESPACE_URL, id_number))
  4. resId = u"".join(re.findall("\d+", resId))
  5. return resId

    上面的代码片段是根据用户的手机生成对应的唯一下单ID。当支付成功后,微信会将支付结果以post请求的发送提交到NOTIFY_URL,对应的View接受参数如下:

  1. class PayResult(APIView):
  2. authentication_classes = []
  3. permission_classes = []
  4. def get(self, request):
  5. self.post(request)
  6. def post(self, request):
  7. if request.body != "":
  8. xmlDict = xmlParse(request.body)
  9. print json.dumps(xmlDict, ensure_ascii=False)
  10. if xmlDict.has_key(u'return_code') and xmlDict[u'return_code'] == u'SUCCESS':
  11. total_fee = xmlDict[u'total_fee']
  12. out_trade_no = xmlDict[u'out_trade_no']
  13. try:
  14. with transaction.atomic():
  15. conn = redis.StrictRedis()
  16. data = conn.get("out_trade_no_" + out_trade_no)
  17. if data is not None:
  18. jsonData = json.loads(data)
  19. admin_user_id = jsonData[u"admin_user"]
  20. if jsonData[u"type"] == u"储值卡充值":
  21. admin_user = AdminUser.objects.filter(id=admin_user_id)[0]
  22. assets = Assets.objects.filter(admin_user=admin_user)
  23. if len(assets) == 0:
  24. assets = Assets()
  25. assets.assets = float(total_fee) / 100
  26. assets.admin_user = admin_user
  27. else:
  28. assets = assets[0]
  29. assets.assets = float(assets.assets) + float(total_fee) / 100
  30. assets.save()
  31. assetsDetail = AssetsDetail()
  32. assetsDetail.balance = float(total_fee) / 100
  33. assetsDetail.type = 1
  34. assetsDetail.note = jsonData[u"type"]
  35. assetsDetail.admin_user = admin_user
  36. assetsDetail.save()
  37. except BaseException as e:
  38. print e.message
  39. return_data = {"return_code": "SUCCESS", "return_msg": "OK"}
  40. return HttpResponse(trans_dict_to_xml(return_data), content_type="application/xml")

    当支付成功时,微信提交的参数中会包含return_code并且值为SUCCESS,这里需要注意的是,微信提交的参数类型为xml,且我们返回给微信的数据类型也为xml,若处理成功,需要返回:

  1. <xml>
  2. <return_code><![CDATA[SUCCESS]]></return_code>
  3. <return_msg><![CDATA[OK]]></return_msg>
  4. </xml>

    注意这里的格式并不是常见的xml格式,而是微信它自己的<![CDATA[,这也是让人很麻烦的地方。将json数据封装成微信的xml格式数据函数如下:

  1. def trans_dict_to_xml(data_dict):
  2. data_xml = []
  3. for k in sorted(data_dict.keys()):
  4. v = data_dict.get(k)
  5. if k == 'detail' and not v.startswith('<![CDATA['):
  6. v = '<![CDATA[{}]]>'.format(v)
  7. data_xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
  8. return '<xml>{}</xml>'.format(''.join(data_xml)).encode('utf-8')

    最后,在前端处理我们后台返回的结果并调起微信支付:

  1. sendAjax({"number":number},"/wx-pay-config",function (data) {
  2. wx.config({
  3. debug:false,
  4. appId:data["appId"],
  5. timestamp:data["timeStamp"],
  6. nonceStr:data["nonceStr"],
  7. signature:data["paySign"],
  8. package:data["package"]
  9. });
  10. wx.ready(function () {
  11. wx.chooseWXPay({
  12. timestamp: data["timeStamp"], // 支付签名时间戳
  13. nonceStr: data["nonceStr"], // 支付签名随机串,不长于32 位
  14. package: data["package"], // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
  15. signType: "MD5", // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
  16. paySign: data["paySign"], // 支付签名
  17. success: function (res) {
  18. //支付成功
  19. mui.alert("支付成功");
  20. },
  21. cancel: function (res) {
  22. //支付取消
  23. mui.alert("支付已取消");
  24. }
  25. });
  26. });
  27. wx.error(function (res) {
  28. console.log("error:" + res);
  29. });
  30. });

sendAjax是我自己封装的一个提交POST形式的ajax函数,大家使用普通的ajax发送post请求就可以了。这里需要注意的是,最后我调用了一个wx.error的函数,我原以为是在支付出错时调用的,但经过测试后发现:即使支付成功了,这个error函数依然会被执行,很容易被误导,以为支付没成功。这里值得一提的是,在微信官方文档中,前端调起支付的代码如下:

  1. function onBridgeReady(){
  2. WeixinJSBridge.invoke(
  3. 'getBrandWCPayRequest', {
  4. "appId":"wx2421b1c4370ec43b", //公众号名称,由商户传入
  5. "timeStamp":"1395712654", //时间戳,自1970年以来的秒数
  6. "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
  7. "package":"prepay_id=u802345jgfjsdfgsdg888",
  8. "signType":"MD5", //微信签名方式:
  9. "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
  10. },
  11. function(res){
  12. if(res.err_msg == "get_brand_wcpay_request:ok" ){
  13. // 使用以上方式判断前端返回,微信团队郑重提示:
  14. //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
  15. }
  16. });
  17. }
  18. if (typeof WeixinJSBridge == "undefined"){
  19. if( document.addEventListener ){
  20. document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
  21. }else if (document.attachEvent){
  22. document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
  23. document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
  24. }
  25. }else{
  26. onBridgeReady();
  27. }

经过测试后发现,这样写并不能调起支付,是一个完全没反应的状态。这是微信文档很坑的地方,需要大家特别注意。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多