寫項目的時候注意的問題
如果寫的問題,大吉大利
有問題,最好是報錯,因為有了報錯,你就知道錯在哪里?開心
最怕的是有問題,但是就是不報錯。找不到問題的所在。
小程序的支付流程圖
題目
背景支付平台,這個個平台相當於支付寶的平台。
1 用戶下單,就記入訂單,如果支付成功就改變訂單狀態。(某個商戶底下的訂單)
2 支付寶商戶。商戶可以對他已支付的訂單,做體現。
3 體現出問題了。
當用戶提現的時候,就出問題,a商戶1000萬,但是體現980萬,10000萬,999.9萬
體現的代碼邏輯
- 查詢已支付支付的訂單,把所有已經支付的訂單,的錢數做累加,狀態是沒有提現的 1 並且返回訂單id
100
- 給商戶的體現錢數+錢數做累加,等於可體現錢數 2
- 將所有該用戶底下的訂單定做,update操作,把已支付的全部改成以體現 3 只改變,返回訂單id
訂單表
錢數 支付狀態 是否體現 商戶id
100 1 1 1
200 1 0 1
100 1 0 1
用戶表
可體現錢數
100
會不會有訂單進來
回顧
1 小程序的登入
1 先調用wx.login來獲取code.
2 將code通過wx.request傳遞到后端。后端通過code,appid,Appsecret來請求code2session接口來獲得
openid,session_key,openid是單個應用用戶的唯一標識
3 后端得到open_id與session_key,進行存儲,並自定義登入狀態,key->val,val是openid與session_key,key相當於token,在把token傳遞給小程序。
4 小程序收到后端返回的token,保存,下次請求后端的時候如果需要登入狀態,把這個token帶上
2 授權
1 微信的部分需要用戶同意后才能使用,哪些接口需要用戶同意呢?我們scope列表中的對應關系。
2 我們可以用wx.getSetting來判斷接口有沒有被用戶授權,查看哪個接口,就需要判斷wx.getSetting的返回值,authsetting中的socop值判斷。
3 如果我們發現這個接口的authsetting中的scope為false,就標識該用戶沒有對該接口授權,這個時候我們就可以用wx.authorize吊起對應授權彈框,如果要對哪個接口授權,就需要給wx.authorize,傳遞對應的scope值,如果
authsetting中的scope為true,表示該用戶已經對該接口授權,我們就可以直接使用對應的接口
4 wx.authorize不能將wx.getUserInfor這接口直接吊起授權彈框。我們只能通過<button>按鈕的行式手動吊起彈框
后端,如何解析wx.getUserInfor中的用戶信息。
1 我們用encryptedData和iv,進行解密,必須要用到session_key,所以用必須是登入狀態。
2 但是session_key是有有效期。而且session_key的有效期,不是一個固定值,他是通過用戶行為來決定,session_key的有效期時間。
3 但是我們可以通過wx.checkSession來判斷有沒有過期。
4 保證session_key沒有過期的情況下。我們將iv,encryptedData,token(登入憑證)發送到后端.
5 后端使用官方提供的sdk,進行解密。
6 解密成功以后保存到數據,數據庫的字符集一定要是utf8mb4,才能保存表情包
如官方的sdk沒有Crypto包用下面的方法解決:
pip install pycryptodome
小程序的app.js
//app.js
App({
/*
當小程序初始話完成,會觸發onlaunch(全局只觸發一次)
*/
onLaunch: function () {
// let that = this
// 登錄
this.my_login()
},
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)
}
})
}
})
},
/**
* 當小程序啟動,或者是重后台進入到前台的時候,會執行onshow,
* 那我們可以通過這個option中的scene值來判斷不同進入場景
*/
// onShow:function(option){
// console.log("小程序onshow,:onShow",option)
// },
// /*小程序重前台進入到后台的時候,會觸發:onHide*/
// onHide:function(){
// console.log("小程序重前台進入到后台的時候,會觸發:onHide")
// },
/**可以在全局使用 */
globalData: {
userInfo: null,
baseurl: "http://127.0.0.1:8000/"
}
})
小程序頁面的js
// pages/test3/test3.js
const app = getApp()
Page({
/**
* 頁面的初始數據
*/
data: {
},
//加密解密
user1:function(e){
// console.log("e",e.detail)
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和encryptedate
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.request({
//這里是發送iv和encryptedate
url: app.globalData.baseurl + "getinfo/",
data: {
iv: res.iv,
encryptedData: res.encryptedData,
token: wx.getStorageSync('token')
},
method: "POST",
success: (e) => {
console.log("后台返回的數據", e)
}
})
}
})
}
}
})
},
//支付
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)
},
})
}
})
}
})
主配置settings.py
# django-redis緩存
STATIC_URL = '/static/'
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "Admin123",
},
},
}
# 數據庫
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'python13',
'USER':'root',
'PASSWORD':'123456',
'HOST':'127.0.0.1',
'PORT': 3306,
'OPTIONS': {'charset': 'utf8mb4'}, # 當用支付的時候用utf8mb4
}
}
有關小程序的settings
AppId="xxx" # 寫你自己的
AppSecret="xxx" # 寫你自己的
code2Session="https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code"
pay_mchid ='xxx' # 這個需要三證合一才可以拿到什么營業執照許可證啥的,一般只有公司才有,
pay_apikey = 'xxx' # 這個需要三證合一才可以拿到什么營業執照許可證啥的,一般只有公司才有,
python支付的demo
from WXBizDataCrypt import WXBizDataCrypt
if __name__ == '__main__':
main()
用於加密解密的校驗
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)
pay
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):
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
self.notify_url = "htttp://www.test.com"
self.trade_type ="JSAPI"
self.openid = self.openid
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
my_ser
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解析模塊
<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就表示獲取內容
小程序支付在總結
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異步回調