|     終于在踩完了無數(shù)坑之后,發(fā)現(xiàn)這個流程必須記錄下來。在這之前,本人不僅嘗試過網(wǎng)上各種各樣的帖子,也參考了微信支付的官方文檔,都最后都是發(fā)現(xiàn):要么難以理解,要么甚至按照微信支付的官方文檔也會遇到問題。     雖然如果完全參考官方的文檔會被誤導,但是其中一部分還是可以參考的。     首先來看要準備的環(huán)境:     1.認證的微信公眾號,并且開通了微信支付     2.開通商戶平臺賬號     3.帶域名的服務器     以上三點是對接微信h5支付的前提條件,接下來我們可以參考微信支付的官方文檔:由于是h5頁面調(diào)用的微信支付,所以這里點擊鏈接后選擇JSAPI支付。在統(tǒng)一下單的列表中,微信給出了需要請求的API以及參數(shù)。由于我參考的其他的技術貼自己請求微信的API一直沒成功,所以最后我還是直接調(diào)用了微信官方對支付封裝好的代碼,代碼可以在github上下載。下載好之后,需要分別將weixin文件夾中的config.py、lib.py和pay.py腳本文件拷貝到我們的工程目錄下。拷貝完成后對這些腳本文件稍作配置:     config.py腳本中需要配置的都是些微信公眾號的信息,像APPID、APPSECRET等,其中都有注釋。登錄到微信公眾平臺,在基本配置中可以看到一下信息:          這里就有APPID、APPSECRET和TOKEN,服務器地址要配置成服務器上的一個接口(服務器帶上域名),在提交配置的時候微信會對我們配置的這個接口發(fā)送一個請求,接口需要接收微信傳來的參數(shù)以及返回正確的響應才能配置成功。服務器上的接口如下:     authentication_classes = []        signature = request.GET.get('signature')        timestamp = request.GET.get('timestamp')        nonce = request.GET.get('nonce')        echostr = request.GET.get('echostr')        wechat_instance = WechatBasic(conf=wxConf)        if not wechat_instance.check_signature(signature=signature, timestamp=timestamp, nonce=nonce):            return HttpResponseBadRequest('Verify Failed')            return HttpResponse(echostr, content_type="application/json")
     wxConf的配置如下:     appid=SOCIAL_AUTH_WEIXIN_APPID,    appsecret=SOCIAL_AUTH_WEIXIN_SECRET,    encoding_aes_key='xxxxxx'
     其中token需要填寫之前公眾號開發(fā)信息中的令牌(Token),appid就是公眾號中的開發(fā)者ID,appsecret為開發(fā)者密碼,encoding_aes_key為服務器配置項中的服務器加解密密鑰。     我這里使用的drf的API View,注意其中的 authentication_classes = []
     這兩句是取消該接口的token認證和登錄認證,否則如果接口上有任何的權限認證,微信的請求都是不成功的。     config.py腳本中剩下的商戶ID MCHID和商戶支付密鑰KEY則需要登錄到微信商戶平臺中獲取。     做好這些配置之后,就可以正式開始View的編寫了: from utils.wechatUtils.pay import JsApi_pub, UnifiedOrder_pubclass WxPayConfig(APIView):        money = request.data[u"number"]        admin_user = AdminUser.objects.filter(username=request.user.username)[0]        master = Master.objects.filter(admin_user=admin_user)[0]        socialAccounts = SocialAccounts.objects.filter(admin_user=admin_user)[0]        openid = socialAccounts.openid        money = int(float(money)*100)        out_trade_no = genOrder(master.phone)        unifiedOrder = UnifiedOrder_pub()        unifiedOrder.setParameter("openid", openid)        unifiedOrder.setParameter("body", "儲值卡充值")        unifiedOrder.setParameter("out_trade_no", out_trade_no)        unifiedOrder.setParameter("total_fee", str(money))        unifiedOrder.setParameter("notify_url", NOTIFY_URL)        unifiedOrder.setParameter("trade_type", "JSAPI")        prepay_id = unifiedOrder.getPrepayId()        jsApi.setPrepayId(prepay_id)        jsApiParameters = jsApi.getParameters()        conn = redis.StrictRedis()        conn.set("out_trade_no_" + out_trade_no, json.dumps({"type": "儲值卡充值", "admin_user": admin_user.id},                                                            ensure_ascii=False), 60 * 10)        return HttpResponse(json.dumps(json.loads(jsApiParameters)), content_type="application/json")
       這里同樣是使用drf的APIView,number參數(shù)是從前端提交的支付金額,從微信提供的pay.py中導入JsApi_pub和UnifiedOrder_pub,然后設置所需的參數(shù)。其中參數(shù)openid就是用戶微信的openid,body是商品名稱,out_trade_no為我們自己服務器生成的訂單號,total_fee就是付款金額,notify_url為微信支付成功后回調(diào)的我們服務器的URL,trade_type參數(shù)為JSAPI表示支付類型為h5頁面支付。填寫完這些參數(shù)后,調(diào)用UnifiedOrder_pub類中的getPrepayId函數(shù)獲得prepay_id,并進行設置。最后將調(diào)用jsApi.getParameters()函數(shù)返回的結果返回到前端。       生成參數(shù)out_trade_no的函數(shù)如下: def genOrder(phone="176******"):    id_number = str(phone) + str(time.time())    resId = str(uuid.uuid3(uuid.NAMESPACE_URL, id_number))    resId = u"".join(re.findall("\d+", resId))
     上面的代碼片段是根據(jù)用戶的手機生成對應的唯一下單ID。當支付成功后,微信會將支付結果以post請求的發(fā)送提交到NOTIFY_URL,對應的View接受參數(shù)如下: class PayResult(APIView):    authentication_classes = []            xmlDict = xmlParse(request.body)            print json.dumps(xmlDict, ensure_ascii=False)            if xmlDict.has_key(u'return_code') and xmlDict[u'return_code'] == u'SUCCESS':                total_fee = xmlDict[u'total_fee']                out_trade_no = xmlDict[u'out_trade_no']                    with transaction.atomic():                        conn = redis.StrictRedis()                        data = conn.get("out_trade_no_" + out_trade_no)                            jsonData = json.loads(data)                            admin_user_id = jsonData[u"admin_user"]                            if jsonData[u"type"] == u"儲值卡充值":                                admin_user = AdminUser.objects.filter(id=admin_user_id)[0]                                assets = Assets.objects.filter(admin_user=admin_user)                                    assets.assets = float(total_fee) / 100                                    assets.admin_user = admin_user                                    assets.assets = float(assets.assets) + float(total_fee) / 100                                assetsDetail = AssetsDetail()                                assetsDetail.balance = float(total_fee) / 100                                assetsDetail.note = jsonData[u"type"]                                assetsDetail.admin_user = admin_user                except BaseException as e:            return_data = {"return_code": "SUCCESS", "return_msg": "OK"}            return HttpResponse(trans_dict_to_xml(return_data), content_type="application/xml")
     當支付成功時,微信提交的參數(shù)中會包含return_code并且值為SUCCESS,這里需要注意的是,微信提交的參數(shù)類型為xml,且我們返回給微信的數(shù)據(jù)類型也為xml,若處理成功,需要返回:   <return_code><![CDATA[SUCCESS]]></return_code>  <return_msg><![CDATA[OK]]></return_msg>
     注意這里的格式并不是常見的xml格式,而是微信它自己的<![CDATA[,這也是讓人很麻煩的地方。將json數(shù)據(jù)封裝成微信的xml格式數(shù)據(jù)函數(shù)如下: def trans_dict_to_xml(data_dict):    for k in sorted(data_dict.keys()):        if k == 'detail' and not v.startswith('<![CDATA['):            v = '<![CDATA[{}]]>'.format(v)        data_xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))    return '<xml>{}</xml>'.format(''.join(data_xml)).encode('utf-8')
     最后,在前端處理我們后臺返回的結果并調(diào)起微信支付: sendAjax({"number":number},"/wx-pay-config",function (data) {                    timestamp:data["timeStamp"],                    nonceStr:data["nonceStr"],                    signature:data["paySign"],                        timestamp: data["timeStamp"], // 支付簽名時間戳                        nonceStr: data["nonceStr"], // 支付簽名隨機串,不長于32 位                        package: data["package"], // 統(tǒng)一支付接口返回的prepay_id參數(shù)值,提交格式如:prepay_id=***)                        signType: "MD5", // 簽名方式,默認為'SHA1',使用新版支付需傳入'MD5'                        paySign: data["paySign"], // 支付簽名                        success: function (res) {                wx.error(function (res) {                    console.log("error:" + res);
 sendAjax是我自己封裝的一個提交POST形式的ajax函數(shù),大家使用普通的ajax發(fā)送post請求就可以了。這里需要注意的是,最后我調(diào)用了一個wx.error的函數(shù),我原以為是在支付出錯時調(diào)用的,但經(jīng)過測試后發(fā)現(xiàn):即使支付成功了,這個error函數(shù)依然會被執(zhí)行,很容易被誤導,以為支付沒成功。這里值得一提的是,在微信官方文檔中,前端調(diào)起支付的代碼如下: function onBridgeReady(){      'getBrandWCPayRequest', {         "appId":"wx2421b1c4370ec43b",     //公眾號名稱,由商戶傳入              "timeStamp":"1395712654",         //時間戳,自1970年以來的秒數(shù)              "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //隨機串              "package":"prepay_id=u802345jgfjsdfgsdg888",              "signType":"MD5",         //微信簽名方式:              "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信簽名       if(res.err_msg == "get_brand_wcpay_request:ok" ){      // 使用以上方式判斷前端返回,微信團隊鄭重提示:            //res.err_msg將在用戶支付成功后返回ok,但并不保證它絕對可靠。if (typeof WeixinJSBridge == "undefined"){   if( document.addEventListener ){       document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);   }else if (document.attachEvent){       document.attachEvent('WeixinJSBridgeReady', onBridgeReady);        document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
 經(jīng)過測試后發(fā)現(xiàn),這樣寫并不能調(diào)起支付,是一個完全沒反應的狀態(tài)。這是微信文檔很坑的地方,需要大家特別注意。 |