注:本文僅提供 pc 端微信掃碼支付(模式一)的示例代碼。php
關於對接過程當中遇到的問題總結在本文最下方。前端
參考: 官方文檔,數據庫
https://blog.csdn.net/lm_is_dc/article/details/83312706api
一。wxpay_settings.py (配置基本參數和建立訂單時必要的方法,如 隨機生成字符串,加密簽名,生成支付二維碼等)
安全
# encoding: utf-8 import random import os import time import requests import hashlib from random import Random import qrcode from bs4 import BeautifulSoup from appname import settings APP_ID = "" # 公衆帳號appid MCH_ID = "" # 商戶號 API_KEY = "" # 微信商戶平臺(pay.weixin.qq.com) -->帳戶設置 -->API安全 -->密鑰設置,設置完成後把密鑰複製到這裏 UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" # url是微信下單api NOTIFY_URL = "http://xxx/get_pay/callback/" # 微信支付結果回調接口,須要你自定義 CREATE_IP = '111.111.11.11' # 你服務器上的ip def random_str(randomlength=8): """ 生成隨機字符串 :param randomlength: 字符串長度 :return: """ str = '' chars = 'AaBbCcDdEeFfGgHhIiJjKbkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' length = len(chars) - 1 random = Random() print "randomlength : ", randomlength for i in range(randomlength): str += chars[random.randint(0, length)] print "str : ", str return str def order_num(phone): """ 生成掃碼付款訂單號 :param phone: 手機號 :return: """ local_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) result = phone + 'T' + local_time + random_str(5) return result def get_sign(data_dict, key): # 簽名函數,參數爲簽名的數據和密鑰 params_list = sorted(data_dict.items(), key=lambda e: e[0], reverse=False) # 參數字典倒排序爲列表 params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + key # 組織參數字符串並在末尾添加商戶交易密鑰 md5 = hashlib.md5() # 使用MD5加密模式 md5.update(params_str.encode('utf-8')) # 將參數字符串傳入 sign = md5.hexdigest().upper() # 完成加密並轉爲大寫 return sign def trans_dict_to_xml(data_dict): # 定義字典轉XML的函數 data_xml = [] for k in sorted(data_dict.keys()): # 遍歷字典排序後的key v = data_dict.get(k) # 取出字典中key對應的value if k == 'detail' and not v.startswith('<![CDATA['): # 添加XML標記 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') # 返回XML,並轉成utf-8,解決中文的問題 def trans_xml_to_dict(data_xml): soup = BeautifulSoup(data_xml, features='xml') xml = soup.find('xml') # 解析XML if not xml: return {} data_dict = dict([(item.name, item.text) for item in xml.find_all()]) return data_dict def wx_pay_unifiedorde(detail): """ 訪問微信支付統一下單接口 :param detail: :return: """ detail['sign'] = get_sign(detail, API_KEY) xml = trans_dict_to_xml(detail) # 轉換字典爲XML response = requests.request('post', UFDODER_URL, data=xml) # 以POST方式向微信公衆平臺服務器發起請求 return response.content def pay_fail(err_msg): """ 微信支付失敗 :param err_msg: 失敗緣由 :return: """ data_dict = {'return_msg': err_msg, 'return_code': 'FAIL'} return trans_dict_to_xml(data_dict) def create_qrcode(username, url): """ 生成掃碼支付二維碼 :param username: 用戶名 :param url: 支付路由 :return: """ get_path = "%s/%s" % (settings.MEDIA_ROOT, "QRcode") # 建立文件夾路徑 img_path = "%s/%s" % (settings.MEDIA_ROOT, "QRcode/") # 建立文件路徑 img = qrcode.make(url) # 建立支付二維碼片 # 你存放二維碼的地址 uploads/media img_url = img_path + username + '.png' # 建立文件 if not os.path.exists(get_path): os.makedirs(get_path) img.save(img_url) ret_url = settings.MEDIA_URL + 'QRcode/' + username + '.png' # 前端展現路徑 return ret_url
二。URL服務器
url(r'^create_order/$', CreateOrderViews.as_view()), # 建立訂單和生成二維碼 url(r'^get_pay/$', Wxpay_QRccode.as_view()), # 微信支付二維碼展現頁 wx url(r'^get_pay/callback/$', Wxpay_ModelOne_pay.as_view()), # 支付回調接口 wx
三。Views微信
class CreateOrderViews(APIView): """ 建立訂單和支付二維碼 """ # 生成訂單(省略部分操做..) order_obj = models.Order.objects.create() # 調用微信支付 (下面部分參數的詳細說明請看第一步驟) paydict = { 'appid': APP_ID, 'mch_id': MCH_ID, 'nonce_str': random_str(), 'product_id': order_obj.id, # 商品id,可自定義 'time_stamp': int(time.time()), } paydict['sign'] = get_sign(paydict, API_KEY) url = "weixin://wxpay/bizpayurl?appid=%s&mch_id=%s&nonce_str=%s&product_id=%s&time_stamp=%s&sign=%s" \ % (paydict['appid'], paydict['mch_id'], paydict['nonce_str'], paydict['product_id'], paydict['time_stamp'], paydict['sign']) # 能夠直接在微信中點擊該url,若是有錯誤,微信會彈出提示框,若是是掃碼,若是失敗,什麼提示都沒有,不利於調試 # 建立二維碼 img_url = create_qrcode(user_obj.username, url) # 調用生成二維碼的方法(具體看第一步 wxpay_settings.py 的create_qrcode方法) order_obj.wx_pay_path = img_url # 將支付二維碼路徑存儲在訂單表中 order_obj.save() # 保存訂單 pay_url = "/order/get_pay/?order_id=%s" % (order_obj.id) return JSONResponse({"pay_url": pay_url}) class Wxpay_QRccode(APIView): """ 返回二維碼圖片路徑接口 """ def get(self, request, *args, **kwargs): username = request.session.get('username') if username: order_id = request.GET.get("order_id") order_obj = Order.objects.filter(id=order_id, create_user__username=username, status=Order.STATUS_DEFAULT).first() return JSONResponse({"pay_img": order_obj.wx_pay_path}) # 從數據庫中獲取圖片路徑 return JSONResponse({"error": "xxx"}) @method_decorator(csrf_exempt, name='dispatch') class Wxpay_ModelOne_pay(APIView): """ 使用微信掃一掃掃描二維碼,微信系統會自動回調此路由,Post請求 """ def post(self, request, *args, **kwargs): """ 掃描二維碼後,微信系統回調的地址處理 微信傳來的參數格式經trans_xml_to_dict()轉成字典 {'openid': 'xxx', 'is_subscribe': 'Y', 'mch_id': 'xxx', 'nonce_str': 'xxx', 'sign': 'xxx', 'product_id': 'xxx', 'appid': 'xxx'} :param request: :param args: :param kwargs: :return: """ try: data_dict = trans_xml_to_dict(request.body) # 回調數據轉字典 sign = data_dict.pop('sign') # 取出簽名 key = API_KEY # 商戶交易密鑰 back_sign = get_sign(data_dict, key) # 計算簽名 if data_dict.get('product_id'): # 第一次微信掃碼請求,確認是否有該訂單(並確認訂單狀態等是否正確) order_obj = Order.objects.filter(id=data_dict.get('product_id'), status=Order.STATUS_DEFAULT).first() if not order_obj: return HttpResponse(pay_fail('交易信息有誤,未找到該訂單')) elif data_dict.get('return_code') == "SUCCESS": # 此處是支付成功後的回調,第二次請求(用於修改訂單狀態,保存openid等) order_obj = Order.objects.filter(vx_out_trade_no=data_dict.get('out_trade_no'), status=Order.STATUS_DEFAULT).first() with transaction.atomic(): # 開啓事務操做 order_obj.status = Order.STATUS_PAYED order_obj.pay_mode = Order.WEIXIN order_obj.save() return HttpResponse("SUCCESS") # 最終記得返回 SUCCESS,不然微信會重複訪問該接口,並返回支付結果。 else: return HttpResponse(pay_fail('支付狀態返回錯誤!')) if sign == back_sign: # 驗證簽名是否與回調簽名相同(第一次請求) params = { 'appid': APP_ID, # APPID 'mch_id': MCH_ID, # 商戶號 'nonce_str': random_str(16), # 隨機字符串 'out_trade_no': order_num(data_dict.get('product_id')), # 訂單編號 'total_fee': int(Decimal(order_obj.total_fee) * 100), # 付款金額,單位是分,必須是整數 'spbill_create_ip': CREATE_IP, # 發送請求服務器的IP地址 'body': 'xxx', # 商品描述 'detail': 'xxx', # 商品詳情 'notify_url': NOTIFY_URL, # 微信支付結果回調接口 'trade_type': 'NATIVE', # 掃碼支付類型 } # 調用微信統一下單支付接口url notify_result = wx_pay_unifiedorde(params) if data_dict.get('product_id'): # 保存微信訂單(用於後續修改訂單狀態) order_obj = Order.objects.filter(id=data_dict.get('product_id'), status=Order.STATUS_DEFAULT).first() order_obj.vx_out_trade_no = params.get('out_trade_no') order_obj.save() return HttpResponse(notify_result) return HttpResponse(pay_fail('交易信息有誤,請從新掃碼')) except Exception as e: return HttpResponse(pay_fail('程序異常'))
四。錯誤總結session
1. 開發中第一個錯誤就是掃碼時的服務器繁忙(好像是叫這個錯誤來着。),解決方案:去微信公衆號後臺設置服務器地址。app
2. 參數錯誤的話就請檢查下是否和微信公衆號裏面的匹配。微信公衆平臺
3. 參數回調問題:NOTIFY_URL 這個參數填異步回調地址(必須是服務器地址,微信公衆號後臺設置),
data_dict = trans_xml_to_dict(request.body) 這個方式是能夠拿到微信返回的全部回調參數,
其實這裏會被請求兩次,第一次是掃碼請求,這裏主要是給讓用戶看到支付前微信生成商品信息(商品價格等),
第二次是掃碼後的支付成功/失敗的回調請求,這裏主要就是對數據庫的操做了(訂單狀態,商品屬性等),
data_dict.get('return_code') == "SUCCESS" ,return_code參數是隻有回調纔會傳,因此根據這個判斷須要作什麼操做。
五。 本文可能有些錯誤的地方,歡迎指出,同時有什麼問題也歡迎提出。