小程序的支付
Posted Hank·Paul
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小程序的支付相关的知识,希望对你有一定的参考价值。
小程序的支付流程图
后端,如何解析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 组织数据,请求统一下单接口,微信官方会同步返回一个prepay_id 3 商户系统生成用户订单 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()吊起支付
以上是关于小程序的支付的主要内容,如果未能解决你的问题,请参考以下文章