微信小程序

烂漫一生 提交于 2020-02-05 03:38:24

引:

什么是小程序

微信小程序是一种不需要下载也不需要安装就可以使用的应用程序,它把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

<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])
}

短信验证码

流程

  • 注册腾讯云,开通腾讯云短信。
  • 创建应用
  • 申请签名(个人:公众号)
  • 申请模板
  • 申请腾讯云API 腾讯云
  • 调用相关接口去发送短信 文档

小程序

//将校验之后的手机号发送到后端
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

python SDK

安装:

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)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!