引:
什么是小程序
微信小程序是一种不需要下载也不需要安装就可以使用的应用程序,它把app的功能集成到小程序里面,然后用户就可以在微信里面实现app的基本功能。
小程序的优势
- 不用去装一些不常用app,直接在微信中就可以实现
- 初创公司开发app成本更低
- 微信成为了app前期推广的重要入口
环境搭建
账号注册
首先需要申请一个微信公众平台账号
立即注册>小程序>填写资料>注册
链接:微信公众平台
在注册成功后注意保留自己的appid
下载开发者工具
链接:开发者工具下载
创建项目
目录文件介绍
pages:存放小程序中每个页面的文件夹
index:小程序中的单独页面,每个页面由四个部分组成:
- .js:页面逻辑代码(js)
- wxml:页面结构(html)
- json:页面配置
- wxss:页面样式表(css)
app.js:小程序全局逻辑
app.json:小程序公共配置
app.wxss:小程序公共样式表
开发小程序
全局配置
app.js文件:
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"tabBar": {
"list": [{
"pagePath": "pagePath",
"text": "text",
"iconPath": "iconPath",
"selectedIconPath": "selectedIconPath"
}]
},
}
pages
用于指定小程序由哪些页面组成,每一项都对应一个页面的 路径(含文件名) 信息。文件名不需要写文件后缀,框架会自动去寻找对于位置的 .json, .js, .wxml, .wxss 四个文件进行处理。
目录结构:
├── app.js
├── app.json
├── app.wxss
├── pages
│ │── index
│ │ ├── index.wxml
│ │ ├── index.js
│ │ ├── index.json
│ │ └── index.wxss
│ └── logs
│ ├── logs.wxml
│ └── logs.js
└── utils
配置:
"pages": [
"pages/index/index",
"pages/logs/logs"
],
window
用于设置小程序的状态栏、导航条、标题、窗口背景色。
属性 | 描述 |
---|---|
navigationBarBackgroundColor | 导航栏背景颜色 |
navigationBarTextStyle | 导航栏标题颜色,仅支持 black / white |
navigationBarTitleText | 导航栏标题文字内容 |
backgroundColor | 窗口的背景色 |
{
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "演示",
"backgroundColor": "#eeeeee"
}
}
tabBar
如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。
属性 | 描述 |
---|---|
color | tab 上的文字默认颜色,仅支持十六进制颜色 |
selectedColor | tab 上的文字选中时的颜色,仅支持十六进制颜色 |
backgroundColor | tab 的背景色,仅支持十六进制颜色 |
borderStyle | tabbar 上边框的颜色, 仅支持 black / white |
list | tab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab |
"tabBar": {
"list": [{
"pagePath": "pagePath",
"text": "text",
"iconPath": "iconPath",
"selectedIconPath": "selectedIconPath"
}]
},
属性 | 描述 |
---|---|
pagePath | 页面路径,必须在 pages 中先定义 |
text | tab 上按钮文字 |
iconPath | 图片路径,icon 大小限制为 40kb,不支持网络图片。 |
selectedIconPath | 选中时的图片路径,icon 大小限制为 40kb,不支持网络图片。 |
创建页面
pages>新建目录>新建page
通过新建page创建的页面会自动注册到app.json的pages列表中结尾
组件
text
编写文本信息,类似于span标签
<!--pages/home/home.wxml-->
<text>pages/home/home.wxml</text>
<text>编写文本信息,类似于span标签</text>
view
容器,类似于div标签
<!--pages/home/home.wxml-->
<view>容器</view>
<view>类似于div标签</view>
image
图片标签
<image src="/static/image/1.png"></image>
navigator
跳转到非tabBar页面
<navigator url="/pages/redirect/redirect?id=666">跳转到新页面</navigator>
progress(进度条)
进度条。组件属性的长度单位默认为px,2.4.0起支持传入单位(rpx/px)。
<progress percent="10"/> <!--进度条百分比-->
<progress percent="20" show-info /> <!--右侧显示百分比-->
<progress percent="40" stroke-width="12" /><!--进度条线的宽度-->
<progress percent="60" color="pink" /><!--进度条颜色-->
<progress percent="80" active /><!--进度条从左往右的动画-->
swiper(轮播图)
<swiper autoplay="true">
<swiper-item>
<image src="/static/1.gif"></image>
</swiper-item>
<swiper-item>
<image src="/static/2.gif"></image>
</swiper-item>
</swiper>
属性 | 说明 |
---|---|
indicator-dots | 是否显示面板指示点 |
indicator-color | 指示点颜色 |
indicator-active-color | 当前选中的指示点颜色 |
autoplay | 是否自动切换 |
interval | 自动切换时间间隔 |
样式
在小程序中标签的样式用法和css几乎相同,但需要注意的是在小程序中要使用rpx
代替px
rpx: 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
flex布局
Flex是Flexible Box的缩写,顾名思义为“弹性布局”,用来为盒装模型提供最大的灵活性。
flex布局并不是小程序专属的,它在网站中同样适用
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
display:
指定为flex布局
flex-direction:
规定主轴的方向:row/column
justify-content:
元素在主轴方向上的排列方式:flex-start/flex-end/space-around/space-between
align-items: center:
元素在副轴方向上的排列方式:flex-start/flex-end/space-around/space-between
绑定事件
对标签绑定点击事件
使用bindtap绑定点击事件,通过data-参数名称 的形式传递参数
<view bindtap="clickMe" data-id="123">点击事件</view>
Page({
...
/**
* 点击绑定的事件
*/
clickMe:function(e){
var nid = e.currentTarget.dataset.nid;
console.log(nid);
}
})
数据绑定
显示数据
wxml:
<view>数据:{{message}}</view>
js:
// pages/bind/bind.js
Page({
/**
* 页面的初始数据
*/
data: {
message:"测试数据",
}
)}
数据更新
wxml:
<view>数据:{{message}}</view>
<button bindtap="changeData">点击修改数据</button>
js:
Page({
data: {
message:"初始数据",
},
changeData:function(){
// 修改数据
this.setData({ message: "修改数据"});
}
})
修改data中的局部数据
data: {
percent:[1,2,3],
},
this.setData({
["percent[0]"]:80,
})
双向绑定
wxml
<view>数据:{{message}}</view>
<input bindinput="changeData" value="{{message}}"></input>
Page({
/**
* 页面的初始数据
*/
data: {
message:"初始数据"
},
changeData:function(e){
console.log(e.detail.value)
//更新数据
this.setData({ message: e.detail.value})
}
})
API
跳转页面
跳转到非tabBar页面
wx.navigateTo({
url: '/pages/Home/Home?Id=' + '1',
}
返回上一页面或多级页面
wx.navigateBack({
delta: 1 //返回的页面数,如果 delta 大于现有页面数,则返回到首页。
});
页面调用栈
获取当前页面栈。数组中第一个元素为首页,最后一个元素为当前页面。
var pages = getCurrentPages();
prevPage = pages[pages.length-2];
获取用户信息
wx.getUserInfo
wxml:
<button open-type="getUserInfo" bindgetuserinfo="getUserInfo">授权登录</button>
js:
getUserInfo:function(){
wx.getUserInfo({
success: function (res) {
// 调用成功后触发
console.log('success', res)
},
fail: function (res) {
// 调用失败后触发
console.log('fail', res)
}
})
}
获取地址位置信息
wx.chooseLocation
wxml:
<view bindtap="getLocalPath">{{localPath}}</view>
js:
data: {
localPath:"请选择位置",
},
getLocalPath:function(){
var that = this;
wx.chooseLocation({
success: function(res) {
that.setData({localPath:res.address});
},
})
},
for指令
wx:for
data: {
dataList:["1","2","3"],
userInfo:{
name:"张三",
age:18
}
},
<view wx:for="{{dataList}}" >{{index}} - {{item}}</view>
<!--指定index和item-->
<view wx:for="{{dataList}}" wx:for-index="idx" wx:for-item="x">{{idx}} - {{x}}</view>
if指令
<view wx:if="{{boolean==true}}">
<view class="bg_black"></view>
</view>
<view wx:elif="{{boolean==false}}">
<view class="bg_red"></view>
</view>
<view wx:else>
<view class="bg_red"></view>
</view>
获取图片
从本地相册选择图片或使用相机拍照。图片只是上传到了内存中。
wx.chooseImage
data: {
imageList: []
},
uploadImage:function(){
var that = this;
wx.chooseImage({
count:9,
//最多可以选择的图片张数
sizeType: ['original', 'compressed'],
//所选的图片的尺寸,original原图,compressed压缩图
sourceType: ['album', 'camera'],
//选择图片的来源,album从相册选图,camera使用相机
success:function(res){
that.setData({
imageList: that.data.imageList.concat(res.tempFilePaths)
});
}
});
},
发送https网络请求
wx.request(Object object)
wx.request({
url: '', //请求路径
data: '', //请求参数
header: {}, //请求头
method: 'GET',//请求方法
dataType: 'json',//返回的数据格式
responseType: 'text',//响应的数据类型
success: function(res) {},
fail: function(res) {},
complete: function(res) {},
})
显示消息提示框
wx.showToast
wx.showToast({
title: '',//提示的内容
icon: '',//图标 success/loading/none
image: '',//自定义图标的本地路径,image 的优先级高于 icon
duration: 0,//提示的延迟时间
mask: true,//是否显示透明蒙层,防止触摸穿透
success: function(res) {},
fail: function(res) {},
complete: function(res) {},
})
本地存储操作
获取值
wx.getStorageSync('userInfo');
设置值
wx.setStorageSync('userInfo',"sdfsfd");
清除值
wx.removeStorageSync("userInfo")
小程序页面生命周期
onLoad:
监听页面加载(一次)
onShow:
监听页面显示(每次展示页面都会加载)
onReady:
监听页面初次渲染完成(一次)
onHide:
监听页面隐藏(每次页面隐藏都会加载)
onUnload:
监听页面卸载(卸载页面触发,小程序关闭)
onPullDownRefresh:
监听用户下拉动作(下拉刷新触发需要配置)
onReachBottom:
页面上拉触底事件的处理函数
配置onPullDownRefresh
全局配置:
在所有页面都生效
//app.json
{
"window": {
"backgroundTextStyle": "dark",//下拉刷新时的加载动画
"enablePullDownRefresh": true
}
}
局部配置:
只会在配置的页面中生效
{
"usingComponents": {},
"enablePullDownRefresh": true
}
停止下拉刷新加载:
wx.stopPullDownRefresh();
小程序公共对象
app.js
App({
/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
},
globalData:{
userInfo: null,
}
})
其他页面操作公共值
var app = getApp();
Page({
data: {
},
onShow:function(){
app.globalData
}
});
闭包
var dataList = ["1", "2", "3"]
for (var i in dataList) {
(function(data){
wx.request({
url: 'xxxxx',
success: function (res) {
console.log(data);
}
})
})(dataList[i])
}
短信验证码
流程
小程序
//将校验之后的手机号发送到后端
wx.request({
url: 'http://127.0.0.1:8000/api/message/',
data: { phone: this.data.phone },
method: 'GET',
success: function (res) {
console.log(res);
}
})
python
安装:
pip install tencentcloud-sdk-python
示例
# -*- coding: utf-8 -*-
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
# 导入对应产品模块的client models。
from tencentcloud.sms.v20190711 import sms_client, models
# 导入可选配置类
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
try:
# 必要步骤:
# 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
# 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
# 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
# 以免泄露密钥对危及你的财产安全。
# CAM密匙查询: https://console.cloud.tencent.com/cam/capi
cred = credential.Credential("secretId", "secretKey")
# cred = credential.Credential(
# os.environ.get(""),
# os.environ.get("")
# )
# 实例化一个http选项,可选的,没有特殊需求可以跳过。
httpProfile = HttpProfile()
httpProfile.reqMethod = "POST" # post请求(默认为post请求)
httpProfile.reqTimeout = 30 # 请求超时时间,单位为秒(默认60秒)
httpProfile.endpoint = "sms.tencentcloudapi.com" # 指定接入地域域名(默认就近接入)
# 非必要步骤:
# 实例化一个客户端配置对象,可以指定超时时间等配置
clientProfile = ClientProfile()
clientProfile.signMethod = "TC3-HMAC-SHA256" # 指定签名算法
clientProfile.language = "en-US"
clientProfile.httpProfile = httpProfile
# 实例化要请求产品(以sms为例)的client对象
# 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,或者引用预设的常量
client = sms_client.SmsClient(cred, "ap-guangzhou", clientProfile)
# 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
# 你可以直接查询SDK源码确定SendSmsRequest有哪些属性可以设置
# 属性可能是基本类型,也可能引用了另一个数据结构
# 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明
req = models.SendSmsRequest()
# 基本类型的设置:
# SDK采用的是指针风格指定参数,即使对于基本类型你也需要用指针来对参数赋值。
# SDK提供对基本类型的指针引用封装函数
# 帮助链接:
# 短信控制台: https://console.cloud.tencent.com/sms/smslist
# sms helper: https://cloud.tencent.com/document/product/382/3773
# 短信应用ID: 短信SdkAppid在 [短信控制台] 添加应用后生成的实际SdkAppid,示例如1400006666
req.SmsSdkAppid = "1400787878"
# 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看
req.Sign = "xxx"
# 短信码号扩展号: 默认未开通,如需开通请联系 [sms helper]
req.ExtendCode = ""
# 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回
req.SessionContext = "xxx"
# 国际/港澳台短信 senderid: 国内短信填空,默认未开通,如需开通请联系 [sms helper]
req.SenderId = ""
# 下发手机号码,采用 e.164 标准,+[国家或地区码][手机号]
# 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
req.PhoneNumberSet = ["+8613711112222"]
# 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看
req.TemplateID = "449739"
# 模板参数: 若无模板参数,则设置为空
req.TemplateParamSet = ["666"]
# 通过client对象调用DescribeInstances方法发起请求。注意请求方法名与请求对象是对应的。
# 返回的resp是一个DescribeInstancesResponse类的实例,与请求对象对应。
resp = client.SendSms(req)
# 输出json格式的字符串回包
print(resp.to_json_string(indent=2))
except TencentCloudSDKException as err:
print(err)
简化
#生成随机验证码
import random
random_code = random.randint(1000,9999)
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.sms.v20190711 import sms_client, models
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
try:
cred = credential.Credential("secretId", "secretKey")
client = sms_client.SmsClient(cred, "ap-guangzhou")
req = models.SendSmsRequest()
# 短信应用ID: 短信SdkAppid在 [短信控制台] 添加应用后生成的实际SdkAppid,示例如1400006666
req.SmsSdkAppid = "1400787878"
# 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看
req.Sign = "xxx"
# 下发手机号码,采用 e.164 标准,+[国家或地区码][手机号]
# 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
req.PhoneNumberSet = ["+8613711112222"]
# 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看
req.TemplateID = "449739"
# 模板参数: 若无模板参数,则设置为空
req.TemplateParamSet = ["666"]
# 通过client对象调用DescribeInstances方法发起请求。注意请求方法名与请求对象是对应的。
# 返回的resp是一个DescribeInstancesResponse类的实例,与请求对象对应。
resp = client.SendSms(req)
# 输出json格式的字符串回包
print(resp.to_json_string(indent=2))
except TencentCloudSDKException as err:
print(err)
腾讯云对象存储
准备
- 注册腾讯云账号 腾讯云
- 进入对象存储页面 菜单>基础>对象存储
- 创建存储桶
使用
小程序对象存储SDK
1 创建js文件 cos-wx-sdk-v5.js
2 导入js文件
var COS = require('./lib/cos-wx-sdk-v5.js')
3 创建COS SDK 实例
官方推荐方式:后端通过获取临时密钥给到前端,前端计算签名。
小程序:
var cos = new COS({
// 必选参数
getAuthorization: function (options, callback) {
wx.request({
url: 'http://127.0.0.1:8000/api/app1/getSecretKey',
data: {
// 可从 options 取需要的参数
},
success: function (result) {
var data = result.data;
var credentials = data.credentials;
callback({
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
XCosSecurityToken: credentials.sessionToken,
ExpiredTime: data.expiredTime,
});
}
});
}
});
python:
安装sdk
pip install -U cos-python-sdk-v5
class GetSecretKey(APIView):
def get(self,request,*args,**kwargs):
from sts.sts import Sts
from django.conf import settings
config = {
# 临时密钥有效时长,单位是秒
'duration_seconds': 1800,
# 固定密钥 id
'secret_id': settings.TENCENT_SECRET_ID,
# 固定密钥 key
'secret_key': settings.TENCENT_SECRET_KEY,
# 设置网络代理
# 'proxy': {
# 'http': 'xx',
# 'https': 'xx'
# },
# 换成你的 bucket
'bucket': 'mini-1251317460',
# 换成 bucket 所在地区
'region': 'ap-chengdu',
# 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径
# 例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
'allow_prefix': '*',
# 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
'allow_actions': [
'name/cos:PostObject',
],
}
sts = Sts(config)
response = sts.get_credential()
return Response(response)
4 上传到桶中:
小程序:
cos.postObject({
Bucket: 'xxx-1312568142',
Region: 'ap-beijing',
Key: '文件名'
FilePath: '文件',
onProgress: function (info) {
}
}, function (err, data) {
console.log(err)
console.log(data)
});
微信支付
准备
- 申请小程序并完成企业认证
- 开通微信支付功能
小程序支付交互图
开发步骤
获取用户的openid
调用接口获取登录凭证(code)
//小程序
wx.login({
success:(result) => {
// 获取一个临时凭证(只能用一次/5分钟)
wx.request({
url: 'http://127.0.0.1:8000/login/',
data: {
wx_code:result.code //用户登录凭证
},
method: 'POST',
dataType: 'json',
responseType: 'text',
success: (res) => {
console.log('登录成功');
}
})
}
})
通过code在后台换取openid
# python
def post(self,request,*args,**kwargs):
wx_code = request.data.get('wx_code')
#通过code获取openid:
info = {
'appid':"", # 微信小程序 appid
'secret':"", # 微信小程序 appSecret
'js_code':wx_code,# 登录时获取的 code
'grant_type':"authorization_code",# 授权类型
}
result = requests.get(url='https://api.weixin.qq.com/sns/jscode2session',params=info)
# 提取openid
openid = result.json()['openid']
return Response({'tye':True})
调用支付统一下单
获取info_dict返回给小程序生成支付二维码
小程序:
wx.request({
url: 'http://127.0.0.1:8000/payment/',
data: {
goodsId: this.data.seletedId //页码选中的商品id
},
method: 'POST',
dataType: 'json',
responseType: 'text',
success: (res) => {
console.log(res.data);
wx.requestPayment(
{
'timeStamp': res.data.timeStamp,
'nonceStr': res.data.nonceStr,
'package': res.data.package,
'signType': res.data.signType,
'paySign': res.data.paySign,
'success': function (res) {
},
'fail': function (res) {
},
'complete': function (res) {
}
})
}
})
python关键代码:
####################### 1.调用支付统一下单 ######################
info = {
'appid': '',
#小程序ID
'mch_id': '',
# 商户号
'device_info': '',
# 设备号、自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
'nonce_str': "".join([chr(random.randint(65, 90)) for _ in range(12)]),
# 随机字符串,长度要求在32位以内。
'sign_type': "MD5",
# 签名类型,默认为MD5
'body': "",
# 商品简单描述
'detail': '',
# 商品详细描述
'attach': '',
# 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
'out_trade_no': order_random_string,
# 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。
'total_fee': goods_object.price,
# 订单总金额,单位为分
'spbill_create_ip': request.META.get('REMOTE_ADDR'),
# 终端IP remote_addr = request.META.get('REMOTE_ADDR')
'notify_url': "http://47.93.4.198:8012/pay/notify/",
# 异步接收微信支付结果通知的回调地址
'trade_type': 'JSAPI',
# 小程序取值如下:JSAPI
'openid':''
# openid
}
####################### 2.第一次进行签名 ######################
# 对字典中的key按照ASCII码从小到大排序
# 将排完序的值使用&拼接得到stringA
# 使用stringA和pay_key拼接得到stringSignTemp:stringA+"&key=192006250b4c09247ec02edce69f6a2d"
# 使用Md5加密stringSignTemp
# 将密文转换为大写,得到签名 sign
# 把签名再添加到info中 info['sign'] = sign值
pay_key = "192006250b4c09247ec02edce69f6a2d" #商户平台设置的密钥key
stringSignTemp = "&".join(["{0}={1}".format(k, info[k]) for k in sorted(info)] + ["{0}={1}".format("key", pay_key, ), ])
import hashlib
m = hashlib.md5()
m.update(stringSignTemp.encode('utf-8'))
info['sign'] = m.hexdigest().upper()
# 2 向 https://api.mch.weixin.qq.com/pay/unifiedorder 发请求 (json转换为xml)
xml_string = "<xml>{0}</xml>".format("".join(["<{0}>{1}</{0}>".format(k, v) for k, v in info.items()]))
prepay = requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder',data=xml_string.encode('utf-8'))
# 3 从结果xml中提取 prepay_id
from xml.etree import ElementTree as ET
root = ET.XML(prepay.content.decode('utf-8'))
prepay_dict = {child.tag:child.text for child in root}
prepay_id = prepay_dict['prepay_id']
######################## 3.再次签名 #######################
info_dict = {
'appId': "",
# 小程序ID
'timeStamp': str(int(time.time())),
# 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间
'nonceStr': "".join([chr(random.randint(65, 90)) for _ in range(12)]),
# 随机字符串,长度为32个字符以下。
'package': 'prepay_id={0}'.format(prepay_id),
# 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
'signType': 'MD5',
# 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致
}
temp = "&".join(
["{0}={1}".format(k, info_dict[k]) for k in sorted(info_dict)] + ["{0}={1}".format("key", pay_key, ), ])
sign2 = md5(temp).upper()
info_dict['paySign'] = sign2
# 得到info_dict
查询支付结果
def post(self,request,*args,**kwargs):
# 1. 获取结果把结果XML转换为字典格式
root = ET.XML(request.body.decode('utf-8'))
result = {child.tag: child.text for child in root}
# 2. 校验签名是否正确,防止恶意请求。
sign = result.pop('sign')
# key为商户平台设置的密钥key
key = ""
temp = "&".join(
["{0}={1}".format(k, result[k]) for k in sorted(result)] + ["{0}={1}".format("key", key, ), ])
local_sign = md5(temp).upper()
# 签名一致
if local_sign == sign:
# 根据订单号,进行订单状态修改等操作
out_trade_no = result.get('out_trade_no')
response = """<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"""
# 返回固定格式的数据
return Response(response)