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