微信小程序支付
登录
openid:
相对于当个微信小程序是唯一的id,但是多个小程序无效
unionid:
对于多个小程序,统一用户的id是相同的(比方说一家公司有多个小程序,那么这个用户在这些小程序的id相同)
登录
登录1,2步实现
一步
小程序登录就执行,写在app的生命周期的onLaunch: function () {}
wx.login({
success: res => {
wx.request({
url: that.globalData.URL+\'login/\',
data:{
code:res.code
},
header:{
\'content-type\':\'application/json\'
},
method:\'post\',
success:function(e){
console.log(e)
wx.setStorageSync("token", e.data.data.token)
}
})
// 发送 res.code 到后台换取 openId, sessionKey, unionId
}
})
二步
const为配置一些常量的文件,注意session_key不可泄露
import requests
from settings import const
#调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key
def login(code):
#向url填入参数
url = const.code2Session.format(const.AppId, const.AppSecret, code)
reponse = requests.get(url=url)
#获取响应的json数据
data = reponse.json()
if data.get(\'session_key\'):
return data
else:
return False
from django.shortcuts import render
from rest_framework.views import APIView
from django.core.cache import cache
from libs import wx_login
import hashlib
import time
from . import models
from rest_framework.response import Response
class login(APIView):
def post(self,request):
#获取请求参数,code
param = request.data
if param.get("code"):
#调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key
data = wx_login.login(param.get(\'code\'))
if data:
#由于不应该把会话密钥下发到小程序,也不应该对外提供这个密钥,所以进行加密处理
# 1 session_key+时间戳等到一个key.(md5
md5 = hashlib.md5()
md5.update(data.get("session_key").encode("utf8"))
md5.update(str(time.time()).encode("utf8"))
key = md5.hexdigest()
# 2 session_key与openid做绑定等到val
val = data.get("session_key") + \'&\' + data.get("openid")
# 3key->val存到redis,
cache.set(key, val)
# 4把openid存到数据库。用于后续的登录校验
user_data = models.Wxuser.objects.filter(openid=data.get("openid")).first()
if not user_data:
models.Wxuser.objects.create(openid=data.get("openid"))
# 5把key返回给小程序
return Response({"code": 200, "msg": "suc", "data": {"token": key}})
else:
return Response({"code": 202, "msg": "code无效"})
else:
return Response({"code": 201, "msg": "缺少参数"})
授权
录音授权案例
<button bind:tap="luying">录音</button>
luying:function(){
wx.getSetting({
success(res) {
console.log("res", res.authSetting[\'scope.record\'])
#判断是否已经授权过
if (!res.authSetting[\'scope.record\']) {
#弹窗
wx.authorize({
scope: \'scope.record\',
#同意授权
success() {
// 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
wx.startRecord()
},
#不同意授权
fail(){
console.log("你没有授权")
}
})
}else{
wx.startRecord()
}
}
})
},
用户信息授权+后端获取用户信息
注意:
用户授权和其他授权不一样,他只能通过按钮点击触发
微信不允许明文传输用户信息到后台,必须通过微信提供的sdk解密获取用户的敏感信息
<button open-type="getUserInfo" bindgetuserinfo="info1">获取用户信</button>
info1:function(res){
console.log(res,"按钮")
#授权成功后,可以随时使用这个获取用户信息
// wx.getUserInfo({
// success: function (res) {
// console.log(res, "用户信息")
// }
// })
var that=this
//检查session_key是否过期
wx.checkSession({
success() {
//session_key 未过期,并且在本生命周期一直有效
wx.request({
url: app.globalData.URL + "userinfo/",
data: {
#包括敏感数据在内的完整用户信息的加密数据
encryptedData: res.detail.encryptedData,
#加密算法的初始向量
iv: res.detail.iv,
#用户token
token:wx.getStorageSync("token")
},
header: {
"content-type": "application/json"
},
method: "POST",
success: function (e) {
console.log(e)
}
})
},
fail() {
// session_key 已经失效,需要重新执行登录流程
// wx.login() //重新登录
}
})
},
解密SDK
下载官方demo,简单封装,pip install pycryptodome
import base64
import json
from Crypto.Cipher import AES
from settings import const
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(const.AppId, sessionKey).decrypt(encryptedData, iv)
解密-存入数据库
from libs.WXBizDataCrypt import WXBizDataCrypt
from . import serializers
class UserInfo(APIView):
def post(self,request):
#获取请求参数
param = request.data
if param.get(\'token\') and param.get("encryptedData") and param.get("iv"):
cache_data=cache.get(param.get(\'token\'))
if cache_data:
# 获取session_key
session_key,openid=cache_data.split("&")
#数据解密
user_info=WXBizDataCrypt.get_info(session_key, param.get("encryptedData"),param.get("iv"))
print(user_info)
#存入数据库
user_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\'],
}
#更新数据库
models.Wxuser.objects.filter(openid=openid).update(**user_data)
data = models.Wxuser.objects.filter(openid=openid).first()
data = serializers.Wxuser_ser(data,many=False).data
return Response({"code": 200, "msg": "suc", "data": data})
else:
return Response({"code": 202, "msg": "token无效"})
else:
return Response({"code": 201, "msg": "缺少参数"})
支付开发
交互图
登录部分,之前的就是,获取openid
pay:function(){
wx.request({
url: app.globalData.URL + "pay/",
data: {
token: wx.getStorageSync("token")
},
header: {
"content-type": "application/json"
},
method: "POST",
success: function (e) {
console.log("pay_data",e)
#收到响应的支付参数
wx.requestPayment(
{
\'timeStamp\': e.data.data.timeStamp,
\'nonceStr\': e.data.data.nonceStr,
\'package\': e.data.data.package,
\'signType\': \'MD5\',
\'paySign\': e.data.data.paySign,
\'success\': function (res) {
console.log("支付成功",res)
},
\'fail\': function (res) {
console.log("支付失败", res)
}
})
}
})
}
后端支付接口
class Pay(APIView):
def post(self,request):
param = request.data
#是否登录,确认用户身份
if param.get("token"):
#获取openid
cache_data = cache.get(param.get("token"))
if cache_data:
# 获取客户端ip,如果是负载均衡,就用HTTP_X_FORWARDED_FOR,如果不是就用下面的
if request.META.get(\'HTTP_X_FORWARDED_FOR\'):
self.ip = request.META[\'HTTP_X_FORWARDED_FOR\']
else:
self.ip = request.META[\'REMOTE_ADDR\']
session_key,self.openid=cache_data.split("&")
#5个参数和sign
data=self.get_pay_data()
return Response({"code": 200, "msg": "suc","data":data})
else:
return Response({"code": 202, "msg": "token无效"})
def get_nonce_str(self,num=30):
# strs = ""
# for i in range(30):
# strs += str(random.randint(0,9))
all_str = "0123456789abcdefghijklmnopqrstuvwxyz"
strs = "".join(random.sample(all_str,num))
return strs
def get_out_trade_no(self):
import time
strs = str(int(time.time()))+self.get_nonce_str(5)
return strs
def get_sign(self):
data_dic = {
"nonce_str": self.nonce_str,
"out_trade_no": self.out_trade_no,
"spbill_create_ip": self.ip,
"notify_url": self.notify_url,
"openid": self.openid,
"body": self.body,
"trade_type": "JSAPI",
"sign_type": "MD5",
"appid": self.appid,
"total_fee": self.total_fee,
"mch_id": self.mch_id
}
str_a = "&".join([ f"{i}={data_dic[i]}" for i in sorted(data_dic)])
str_b = f"{str_a}&key={settings.pay_apikey}"
md5 = hashlib.md5()
md5.update(str_b.encode("utf8"))
return md5.hexdigest().upper()
def xml_to_dic(self,xml_data):
import xml.etree.ElementTree as ET
xml_data = ET.fromstring(xml_data)
dic = {}
for child in xml_data:
dic[child.tag] =child.text
return dic
def get_prepay_data(self):
url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
response = requests.post(url=url,data=self.body_data.encode("utf8"),headers={"content-type":"application/xml"})
xml_data = response.content
dic_data = self.xml_to_dic(xml_data)
return dic_data
def get_second_sign(self):
self.second_nonceStr = self.get_nonce_str()
self.timeStamp = str(int(time.time()))
data_dic = {
"appId":settings.AppId,
"timeStamp":self.timeStamp,
"nonceStr":self.second_nonceStr,
"package":f"prepay_id={self.prepay_data.get(\'prepay_id\')}",
"signType":"MD5"
}
print(data_dic)
str_a = "&".join([f"{i}={data_dic[i]}" for i in sorted(data_dic)])
str_b = f"{str_a}&key={settings.pay_apikey}"
md5 = hashlib.md5()
md5.update(str_b.encode("utf8"))
return md5.hexdigest().upper()
def get_pay_data(self):
#小程序id
self.appid = settings.AppId
#商户id
self.mch_id = settings.pay_mchid
self.nonce_str = self.get_nonce_str()
self.sign_type = "MD5"
self.body = "商品描述信息"
self.out_trade_no = self.get_out_trade_no()
self.total_fee = 1
self.spbill_create_ip = self.ip
self.notify_url = "http://www.weixin.qq.com/wxpay/pay.php"
self.trade_type = "JSAPI"
self.sign = self.get_sign()
#数据封装成xml格式
self.body_data = f"""
<xml>
<appid>{self.appid}</appid>
<mch_id>{self.mch_id}</mch_id>
<nonce_str>{self.nonce_str}</nonce_str>
<sign>{self.sign}</sign>
<body>{self.body}</body>
<out_trade_no>{self.out_trade_no}</out_trade_no>
<total_fee>1</total_fee>
<sign_type>MD5</sign_type>
<spbill_create_ip>{ self.spbill_create_ip}</spbill_create_ip>
<notify_url>{self.notify_url}</notify_url>
<openid>{self.openid}</openid>
<trade_type>JSAPI</trade_type>
</xml>"""
#获取prepay_id
self.prepay_data=self.get_prepay_data()
#数据再次签名
second_sign=self.get_second_sign()
data = {
"timeStamp":self.timeStamp,
"nonceStr":self.second_nonceStr,
"package":f"prepay_id={self.prepay_data.get(\'prepay_id\')}",
"paySign":second_sign
}
return data
请发表评论