python - 對接微信支付(PC)和 注意點

注:本文僅提供 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參數是隻有回調纔會傳,因此根據這個判斷須要作什麼操做。

 

五。 本文可能有些錯誤的地方,歡迎指出,同時有什麼問題也歡迎提出。
相關文章
相關標籤/搜索