1 我們用encryptedData和iv,進行解密,必須要用到session_key,所以用必須是登入狀態。 2 但是session_key是有有效期。而且session_key的有效期,不是一個固定值,他是通過用戶行為來決定,session_key的有效期時間。 3 但是我們可以通過wx.checkSession來判斷有沒有過期。 4 保證session_key沒有過期的情況下。我們將iv,encryptedData,token(登入憑證)發送到后端. 5 后端使用官方提供的sdk,進行解密。
地址 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html 6 解密成功以后保存到數據,數據庫的字符集一定要是utf8mb4,因為utf8默認是3字節,微信這類帶表情包名字必須4字節才能保存表情包
沒有敏感信息,無法獲得用戶信息
可以得到用戶信息
數據庫utf8mb4:
當要用到微信名字,表情包時,創建數據庫設為utf8mb4格式

django中settings文件配置
import pymysql pymysql.install_as_MySQLdb() DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'python13', 'USER':'root', 'PASSWORD':'', 'HOST':'127.0.0.1', 'PORT': 3306, 'OPTIONS': {'charset': 'utf8mb4'}, # 設為4字節,兼容符號 } }
如官方的sdk沒有Crypto包用下面的方法解決:
pip install pycryptodome
代碼示例:
微信小程序前端:
app.js
//app.js App({ /* 當小程序初始化完成,會觸發onlaunch(全局只觸發一次) */ onLaunch: function () { // 登錄 this.my_login() //this 就是當前app對象 }, my_login:function(){ let that = this wx.login({ success: res => { // 發送 res.code 到后台換取 openId, sessionKey, unionId console.log(res.code) wx.request({ url: that.globalData.baseurl + 'login/', data: { 'code': res.code }, method: "POST", success(e) { wx.setStorageSync('token', e.data.data.token) } }) } }) console.log("小程序的初始化:onlaunch") }, /**可以在全局使用 */ globalData: { userInfo: null, baseurl:"http://127.0.0.1:8000/" } })
test3.wxml
<button open-type="getUserInfo" bindgetuserinfo='user1'>用戶信息</button>
test3.js
// pages/test3/test3.js const app = getApp() // 引用app模塊 Page({ user1:function(e){ // this 當前頁面,也就是page對象 // console.log('e', e.detail) // 等同於wx.getUserInfo方法 wx.getSetting({ success(res) { if (res.authSetting['scope.userInfo']) { // 獲取用戶信息權限 wx.checkSession({ // 確認用戶session_key有無過期 success() { //session_key 未過期,並且在本生命周期一直有效 wx.getUserInfo({ success: (res) => { console.log('res', res) // 這個res里面就是用戶信息 // 將數據發送到后端 wx.request({ // 這里是發送iv,encryptedData url: app.globalData.baseurl+'getinfo/', data:{ iv:res.iv, encryptedData:res.encryptedData, token: wx.getStorageSync('token') }, method:"POST", success:(e) => { console.log('后台返回的數據',e) } }) } }) }, fail() { // session_key 已經失效,需要重新執行登錄流程 app.my_login() // 通過開頭導入const app = getApp(),獲取app對象 wx.getUserInfo({ success: (res) => { console.log('res', res) // 這個res里面就是用戶信息 ////這里是發送iv,encryptedData,還沒寫 wx.request({ url: 'url', }) } }) } }) } } }) }, })
django后端:

urls.py
from django.conf.urls import url from django.contrib import admin from app01.views import test,user,pay urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^getinfo/', user.Info.as_view()), url(r'^pay/',pay.Pay.as_view()) ]
views/user.py
from rest_framework.views import APIView from rest_framework.response import Response from app01.wx import wx_login from django.core.cache import cache import hashlib,time from app01.models import Wxuser from app01.wx import WXBizDataCrypt from app01.my_ser import wx_user_ser class Login(APIView): def post(self,request): param = request.data if not param.get("code"): return Response({"status":1,"msg":"缺少參數"}) else: code = param.get("code") user_data = wx_login.get_login_info(code) if user_data: val = user_data['session_key'] +"&"+user_data['openid'] md5 = hashlib.md5() md5.update(str(time.clock()).encode("utf-8")) # 當前cpu的時間 md5.update(user_data['session_key'].encode("utf-8")) key = md5.hexdigest() cache.set(key,val,7200) has_user = Wxuser.objects.filter(openid=user_data['openid']).first() if not has_user: Wxuser.objects.create(openid=user_data['openid']) return Response({ "status":0, "msg":"ok", "data":{"token":key} }) else: return Response({"status":2,"msg":"無效的code"}) class Info(APIView): def post(self,request): param = request.data if param.get('iv') and param.get('token') and param.get('encryptedData'): session_key_openid = cache.get(param.get('token')) if session_key_openid: session_key,openid = session_key_openid.split('&') # 解密 user_info = WXBizDataCrypt.WXBizDataCrypt.get_info(session_key,param.get('encryptedData'),param.get('iv')) save_data = { "name": user_info['nickName'], "avatar": user_info['avatarUrl'], "language": user_info['language'], "province": user_info['province'], "city": user_info['city'], "country": user_info['country'], } Wxuser.objects.filter(openid=openid).update(**save_data) user = Wxuser.objects.filter(openid=openid).first() user = wx_user_ser(instance=user,many=False).data return Response({ 'status':0, 'msg':'ok', 'data':user }) else: return Response({'code':2,'msg':'無效的token'}) else: return Response({'code':1,'msg':'缺少參數'})
wx/settings.py
AppId="..." AppSecret='...' code2Session = 'https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code' pay_mchid ='1415981402' pay_apikey = 'xi34nu5jn7x2uujd8u4jiijd2u5d6j8e'
wx/wx_login.py
from app01.wx import settings import requests def get_login_info(code): code_url = settings.code2Session.format(settings.AppId,settings.AppSecret,code) response = requests.get(code_url) json_response = response.json() # 把json格式數據轉換為字典 print("json_response",json_response) if json_response.get("session_key"): return json_response else: return False
wx/WXBizDataCrypt.py
import base64 import json from Crypto.Cipher import AES from app01.wx import settings class WXBizDataCrypt: def __init__(self, appId, sessionKey): self.appId = appId self.sessionKey = sessionKey def decrypt(self, encryptedData, iv): # base64 decode sessionKey = base64.b64decode(self.sessionKey) encryptedData = base64.b64decode(encryptedData) iv = base64.b64decode(iv) cipher = AES.new(sessionKey, AES.MODE_CBC, iv) decrypted = json.loads(self._unpad(cipher.decrypt(encryptedData))) if decrypted['watermark']['appid'] != self.appId: raise Exception('Invalid Buffer') return decrypted def _unpad(self, s): return s[:-ord(s[len(s)-1:])] @classmethod def get_info(cls,sessionKey,encryptedData,iv): return cls(settings.AppId, sessionKey).decrypt(encryptedData, iv)
models.py
from django.db import models # Create your models here. class Wxuser(models.Model): id = models.AutoField(primary_key=True) openid=models.CharField(max_length=255) name = models.CharField(max_length=50) avatar = models.CharField(max_length=200) language = models.CharField(max_length=50) province = models.CharField(max_length=50) city = models.CharField(max_length=50) country = models.CharField(max_length=50) #gender = models.CharField(max_length=50) creat_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) def __str__(self): return self.openid
my_ser.py
from rest_framework import serializers from app01 import models class wx_user_ser(serializers.ModelSerializer): class Meta: model = models.Wxuser fields = "__all__"

1 用戶發起請求下單支付 2 我們要保證用是登入狀態。 3 組織數據,請求統一下單接口,微信官方會同步返回一個prepay_id 4 重新組織數據,進行簽名,將重新組織的數據返回給小程序,小程序在吊起支付。 5 用戶就可以進行支付,支付結果會同步返回給小程序 6 后台修改訂單支付狀態是通過微信官方服務器的異步通知
<xml> <appid name="屬性值" >{.child.text}</appid> child.tag表示appid </xml> import xml.etree.ElementTree as ET 如果我們要解析一個xml文件 tree = ET.parse('country_data.xml') root = tree.getroot() 如果解析字符串 root = ET.fromstring(country_data_as_string) 這個root是 Element for child in root: print(child.tag, child.attrib) #child.tag表是標簽名,child.attrib表示獲取屬性 #child.text就表示獲取內容
代碼示例:
微信前端
test3.wxml
<button bindtap="pay" >下單支付</button>
test3.js
// pages/test3/test3.js const app = getApp() Page({ /** * 頁面的初始數據 */ data: { }, user1:function (e) { //this當前頁面page,對象 // console.log('e', e.detail) // 等同於wx.getUserInfo方法 wx.getSetting({ success(res) { if (res.authSetting['scope.userInfo']) { // 獲取用戶信息權限 wx.checkSession({ success () { //session_key 未過期,並且在本生命周期一直有效 wx.getUserInfo({ success: (res) => { console.log("res",res)//這個res里面就是用戶信息 //將數據發送后端 wx.request({ //這里是發送iv,encryptedData,還沒寫 url: app.globalData.baseurl+"getinfo/", data:{ iv:res.iv, encryptedData:res.encryptedData, token: wx.getStorageSync('token') }, method:"POST", success: (e) => { console.log("后台返回的數據",e) } }) }, }) }, fail () { // session_key 已經失效,需要重新執行登錄流程 app.my_login() wx.getUserInfo({ success: (res) => { console.log("res",res)//這個res里面就是用戶信息 ////這里是發送iv,encryptedData,還沒寫 wx.request({ url: 'url', }) }, }) } }) } } }) }, pay:function(){ wx.request({ url: app.globalData.baseurl+"pay/", data:{"money":1,token:wx.getStorageSync('token')}, method:"POST", success (e) { console.log("支付數據",e) wx.requestPayment( { 'timeStamp':e.data.data.timeStamp, 'nonceStr': e.data.data.nonceStr, 'package': e.data.data.package, 'signType': e.data.data.signType, 'paySign': e.data.data.paySign, 'success':function(res){ console.log("成功",res) }, 'fail':function(res){ console.log("失敗",res) }, }) } }) } })
django后端:
views/pay.py
from rest_framework.views import APIView from rest_framework.response import Response from django.core.cache import cache from app01.wx import settings import hashlib,requests,time class Pay(APIView): def post(self,request): param = request.data if param.get("token") and param.get("money"): openid_session_key = cache.get(param.get("token")) if openid_session_key: # 獲取客戶端ip,如果是負載均衡,就用HTTP_X_FORWARDED_FOR,如果不是就用下面的 # nginx 轉發:--》訪問是nginx,->nginx -> uwsgi if request.META.get('HTTP_X_FORWARDED_FOR'): #有負載均衡就用這個 self.ip = request.META['HTTP_X_FORWARDED_FOR'] else: #沒有負載均衡就用這個 self.ip = request.META['REMOTE_ADDR'] self.openid =openid_session_key.split("&")[1] self.money =param.get("money") data = self.get_pay_data() return Response({"code":0,"msg":"ok","data":data}) else: return Response({"code": 2, "msg": "token無效"}) else: return Response({"code":1,"msg":"缺少參數"}) def get_nonce_str(self): import random data = "123456789abcdefghijklmn" nonce_str = "".join(random.sample(data,10)) #random.sample(從哪里取,取多小個),變成列表 return nonce_str def get_order_id(self): import time import random data = "123456789abcdefghijklmn" order_no = str(time.strftime("%Y%m%d%H%M%S"))+"".join(random.sample(data, 5)) return order_no def get_sign(self): data_dict ={ "appid" : self.appid, "mch_id":self.mch_id, "nonce_str" : self.nonce_str, "body" : self.body, "out_trade_no" : self.out_trade_no, "total_fee" : self.total_fee, "spbill_create_ip" : self.ip, "notify_url" : self.notify_url, "trade_type" : self.trade_type, "openid" : self.openid, } sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)]) sign_str = f"{sign_str}&key={settings.pay_apikey}" print("sign_str", sign_str) md5 = hashlib.md5() md5.update(sign_str.encode("utf-8")) sign = md5.hexdigest() return sign.upper() def xml_to_dict(self,xml_data): import xml.etree.ElementTree as ET xml_dict ={} root = ET.fromstring(xml_data) for child in root: xml_dict[child.tag]= child.text return xml_dict def get_two_sign(self,data): data_dict = { "appId":settings.AppId, "timeStamp":str(int(time.time())), # 根據文檔要求整型樣子的字符串格式 "nonceStr":data['nonce_str'], "package":f"prepay_id={data['prepay_id']}", "signType":"MD5" } sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)]) sign_str = f"{sign_str}&key={settings.pay_apikey}" md5 = hashlib.md5() md5.update(sign_str.encode("utf-8")) sign = md5.hexdigest() return sign.upper() , data_dict['timeStamp'] def get_pay_data(self): # 參考 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1 self.appid = settings.AppId self.mch_id = settings.pay_mchid self.nonce_str = self.get_nonce_str() self.body = "老男孩學費" self.out_trade_no = self.get_order_id() self.total_fee =self.money self.spbill_create_ip =self.ip # 調用微信支付API的機器IP self.notify_url = "htttp://www.test.com" # 異步接收微信支付結果通知的回調地址 self.trade_type ="JSAPI" self.openid = self.openid # 用戶id self.sign = self.get_sign() body_data = f''' <xml> <appid>{self.appid}</appid> <mch_id>{self.mch_id}</mch_id> <nonce_str>{self.nonce_str}</nonce_str> <body>{self.body}</body> <out_trade_no>{self.out_trade_no}</out_trade_no> <total_fee>{self.total_fee}</total_fee> <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip> <notify_url>{self.notify_url}</notify_url> <trade_type>{self.trade_type }</trade_type> <openid>{self.openid }</openid> <sign>{self.sign}</sign> </xml> ''' url = "https://api.mch.weixin.qq.com/pay/unifiedorder" # 如果發送的xml數據要把數據轉化二進制。body_data.encode("utf-8") # request response = requests.post(url,data=body_data.encode("utf-8"),headers = {"content-type":"application/xml"} ) #接收一個二進制的響應 data_dict = self.xml_to_dict(response.content) pay_sign,timeStamp = self.get_two_sign(data_dict) # 再次簽名 data = { "timeStamp": timeStamp, "nonceStr": data_dict['nonce_str'], "package": f"prepay_id={data_dict['prepay_id']}", "signType": "MD5", "paySign":pay_sign } return data
其他代碼同上
1 接收到支付請求。我們先組織數據,然后進行統一下單前的簽名 - 請求的數據與響應的數據都是xml.請求的時候,xml數據要變成二進制,heards中的content-type:"application/xml" -響應的數據也是xml,我要用xml.etree.ElementTree將他轉化為字典 2 拿到統一下單數據,最重要的prepay_id,進行再次簽名。把一下數據發送給小程序。 "timeStamp": 時間戳 "nonceStr":隨機字符串 "package": f"prepay_id={data_dict['prepay_id']}",統一下單中的到的prepay_id "signType": "MD5", "paySign":通過上面數據進行加密的結果 3 小程序掉用wx.resquestPayment()吊起支付
1 統一下單 簽名:80%。 簽名方式沒有搞懂。用了哪些數據,傳過去的數據,和簽名的數據不一致。 appid = 123 傳過去,appid =456 appid 123 --->apid 每一個數據的類型,長短,意義是什么?一定要搞清楚, 2異步回調

