- Unable to verify signature 微信调取支付失败
PayController.php Line:72 Function:log Args:["{\"err_desc\":\"Unable to verify signature\",\"err_msg\":\"get_brand_wcpay_request:fail\",\"err_code\":\"-1\"}","paylog"] Log Description: LogMsg:{"err_desc":"Unable to verify signature","err_msg":"get_brand_wcpay_request:fail","err_code":"-1"} {"appId":"wxfa445c122a1c923b","nonceStr":"gd9asewju3e8s700e6jcbw46gb7y2wkc","package":"prepay_id=wx1112550176338405fbbe409f3847662289","signType":"MD5","timeStamp":"1557550501","paySign":"4331C0806558D7EE73440E5E1D457AA5"} 返回的参加就是说不能验证签名,不知道为什么,用的是官方的demo,下单都成了, 有没有大神知道怎么搞,我搞了两天了
2019-05-11 - 小白的小程序二维码canvas绘制之旅--node.js
要做一个分享到朋友圈的功能,但是微信没有相关的接口,只能通过将相关页面生成小程序码,然后生成图片,让用户保存到相册,再分享到朋友圈。于是,经过三天的折腾终于搞出来了,虽然不知道是不是正确的方法,还是分享出来,给同样没有经验的童鞋作参考。 首先是遇到的一些坑: 1. 开始的时候直接在前端调用接口,但是发现 api.weixin.qq.com 添加到请求域名,因此只能从后端获取。 2. 后端的请求我不会,T^T,这个确实是我自己太菜了,我用的 node.js,搜索发现使用 var request = require('request') 就可以了。 3. 后端请求的时候,因为这是异步操作,导致微信接口还没有返回结果,我这边接口就已经返回小程序端了,这里需要 promise 搭配 await 来实现进程阻塞。 4. 获取到token之后,使用生成二维码的接口 getwxacodeunlimit,得到返回值是一堆乱码,我以为这是正常的数据,于是开始想办法处理这些数据。网上搜到的是用 pipe 和 fs 存到服务器,在从前端把图片从服务器下载下来,在canvas画,但是我用的腾讯云小程序方案,根本不知道图片存到了哪里,也不知道下载链接。所以我就尝试将乱码发送到前端,用 arrayBufferToBase64 处理,发现根本没有输出。。。最后折腾了好久,发现是最开始获取的时候,接受数据的编码格式不对,在 request 的请求参数 opts 里添加一句 :encoding: null ,就可以正确接受,结果是一个buffer变量,里面是多维数字数组。 5. 得到数据之后,我以为只要能在前端显示,就能在canvas里画了,于是就把buffer发送到前端,然后用arrayBufferToBase64 处理,再加上 "data:image/png;base64," 头,就可以作为 image 标签的 src 参数进行显示了,然而,这种 base64 图片,在开发工具上可以画在 canvas里,真机上却不行。官方不支持。。。。所以饶了一圈,又得回到原点。最后还是通过在后端,将得到的buffer上传到cos对象存储,再将存储地址返回到前端,前端通过wx.downloadFile将图片下载下来,再画到 canvas 上。 然后是一些要注意的点: 1. 真机获取二维码只能通过后端实现。 2. canvas画图是异步操作,需要延时后执行 canvasToTempFilePath 3. 除了B接口,另外两个是有个数限制的,要小心调用,用完就没了。 4. token调用也是有次数限制的,需要做处理。 5. canvas大小需要自己手动根据屏幕尺寸进行调整。 6. 我用的wafer 2 ,node.js后端,cos存储 7. 生成的二维码进入的是已经发布的版本,我的是已经有一个发布的版本的。 最后是代码(水平有限): 后端,放在server/controllers/getqrcode.js: [代码]const { mysql } = require([代码][代码]'../qcloud'[代码][代码])[代码][代码]//腾讯云对象存储接口[代码][代码]const COS = require([代码][代码]'cos-nodejs-sdk-v5'[代码][代码])[代码][代码]// 获取基础配置[代码][代码]const configs = require([代码][代码]'../config'[代码][代码])[代码][代码]//请求包[代码][代码]var[代码] [代码]request = require([代码][代码]'request'[代码][代码])[代码] [代码]// token有效时间[代码][代码]var[代码] [代码]period = 7200000; [代码][代码]//小程序开发信息[代码][代码]const APPID = [代码][代码]'wxxxxxxxxxxxxxxxxxx'[代码][代码]const APPSECRET = [代码][代码]'b3dxxxxxxxxxxxxxxxxxxxxxx'[代码] [代码]module.exports = async ctx => {[代码][代码] [代码][代码]//小程序传入的参数,path是页面路径放到page里,pptid是参数,放到scene里的[代码][代码] [代码][代码]const pptid = ctx.query.pptid[代码][代码] [代码][代码]const path = ctx.query.path[代码][代码] [代码][代码]let result = [代码][代码]''[代码][代码] [代码][代码]let msg = [代码][代码]'init'[代码][代码] [代码][代码]let id = 1[代码][代码] [代码][代码]let token =[代码][代码]'none'[代码][代码] [代码][代码]var[代码] [代码]urlpath = [代码][代码]'none'[代码][代码] [代码][代码]console.log([代码][代码]'getqrcode\r\n'[代码][代码])[代码][代码] [代码][代码]const getAccessToken = [代码][代码]function[代码] [代码]() {[代码][代码] [代码][代码]//获取token[代码][代码] [代码][代码]var[代码] [代码]opts = {[代码][代码] [代码][代码]method: [代码][代码]'GET'[代码][代码],[代码][代码] [代码][代码]url: [代码][代码]'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='[代码] [代码]+ APPID + [代码][代码]'&secret='[代码] [代码]+ APPSECRET,[代码][代码] [代码][代码]header: {[代码][代码] [代码][代码]'content-type'[代码][代码]: [代码][代码]'application/json'[代码] [代码]// 默认值[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]console.log([代码][代码]'do get AccessToken!\r\n'[代码][代码])[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve, reject) => {[代码][代码] [代码][代码]request.get(opts, [代码][代码]function[代码] [代码](err, response, body) {[代码][代码] [代码][代码]if[代码] [代码](!err && response.statusCode == 200) {[代码][代码] [代码][代码]if[代码] [代码](body !== [代码][代码]'null'[代码][代码]) {[代码][代码] [代码][代码]results = body;[代码][代码] [代码][代码]code = JSON.parse(results);[代码][代码] [代码][代码]if[代码] [代码](code.access_token != [代码][代码]null[代码][代码]) {[代码][代码] [代码][代码]let accessToken = code.access_token[代码][代码] [代码][代码]resolve(accessToken)[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'joberror'[代码][代码] [代码][代码]console.log([代码][代码]'job error:'[代码] [代码]+ results);[代码][代码] [代码][代码]reject(results)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'getfail'[代码][代码] [代码][代码]reject(msg)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'netfail'[代码][代码] [代码][代码]reject(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]})[代码][代码] [代码][代码]};[代码][代码] [代码][代码]const getqrc = [代码][代码]function[代码] [代码](token) {[代码][代码] [代码][代码]//请求二维码的参数 [代码][代码] [代码][代码]var[代码] [代码]postData = {[代码][代码] [代码][代码]page: path,[代码][代码] [代码][代码]width: 430,[代码][代码] [代码][代码]scene: pptid[代码][代码] [代码][代码]} [代码][代码] [代码][代码]var[代码] [代码]opts = {[代码][代码] [代码][代码]method: [代码][代码]'POST'[代码][代码],[代码][代码] [代码][代码]url: [代码][代码]'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='[代码] [代码]+ token,[代码][代码] [代码][代码]body: JSON.stringify(postData),[代码][代码] [代码][代码]encoding:[代码][代码]null[代码][代码],[代码][代码] [代码][代码]// responseType:"arraybuffer",//这个好像没用[代码][代码] [代码][代码]}[代码][代码] [代码][代码]console.log([代码][代码]'do get QRcode!\r\n'[代码][代码])[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve, reject) => { [代码][代码] [代码][代码]request(opts, [代码][代码]function[代码] [代码](err, response, body) {[代码][代码] [代码][代码]if[代码] [代码](!err && response.statusCode == 200) {[代码][代码] [代码][代码]if[代码] [代码](body !== [代码][代码]'null'[代码][代码]) {[代码][代码] [代码][代码]result = body[代码][代码] [代码][代码]// console.log(body)[代码][代码] [代码][代码]msg = [代码][代码]'qrdone'[代码][代码] [代码][代码]resolve(body)[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'qrgetfail'[代码][代码] [代码][代码]reject(msg)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'qrnetfail'[代码][代码] [代码][代码]reject(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码]//.pipe(fs.createWriteStream('./temp2code.jpg'))[代码] [代码] [代码][代码]})[代码][代码] [代码][代码]};[代码][代码] [代码][代码]const uploadimg=[代码][代码]function[代码][代码](body){[代码][代码] [代码][代码]//上传到cos[代码][代码] [代码][代码]//body就是上一步获取到的body[代码][代码] [代码][代码]//存到cos后自动就生成图片了 [代码][代码] [代码][代码]var[代码] [代码]cos = [代码][代码]new[代码] [代码]COS({[代码][代码] [代码][代码]// 必选参数[代码][代码] [代码][代码]SecretId: configs.cos.SecretId,[代码][代码] [代码][代码]SecretKey: configs.cos.SecretKey,[代码][代码] [代码][代码]// 可选参数[代码][代码] [代码][代码]FileParallelLimit: 3, [代码][代码]// 控制文件上传并发数[代码][代码] [代码][代码]ChunkParallelLimit: 8, [代码][代码]// 控制单个文件下分片上传并发数,在同园区上传可以设置较大的并发数[代码][代码] [代码][代码]ChunkSize: 1024 * 1024, [代码][代码]// 控制分片大小,单位 B,在同园区上传可以设置较大的分片大小[代码][代码] [代码][代码]});[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve, reject) => { [代码][代码] [代码][代码]cos.putObject({[代码][代码] [代码][代码]Bucket: configs.cos.Bucket, [代码][代码]// Bucket 格式:test-1250000000[代码][代码] [代码][代码]Region: configs.cos.region,[代码][代码] [代码][代码]Key: [代码][代码]'qrcode-'[代码][代码]+pptid+[代码][代码]'.jpg'[代码][代码], [代码][代码]/* 给文件起个名字 */[代码][代码] [代码][代码]Body: body, [代码][代码]/* 必须 */[代码][代码] [代码][代码]}, [代码][代码]function[代码] [代码](err, data) {[代码][代码] [代码][代码]if[代码] [代码](err) {[代码][代码] [代码][代码]reject(err)[代码][代码] [代码][代码]console.log(err);[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]resolve(data)[代码][代码] [代码][代码]// console.log(data);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]//查询数据库中的token是否需要更新[代码][代码] [代码][代码]let res = await mysql([代码][代码]"systemSet"[代码][代码]).where({ id }).first()[代码][代码] [代码][代码]console.log([代码][代码]'sql updated on:'[代码] [代码]+ res.updatetime + [代码][代码]'\r\n'[代码][代码])[代码][代码] [代码][代码]var[代码] [代码]timestamp = Date.parse([代码][代码]new[代码] [代码]Date());[代码][代码] [代码][代码]if[代码] [代码](timestamp - Date.parse(res.updatetime) < period){[代码][代码] [代码][代码]//不需要更新[代码][代码] [代码][代码]msg=[代码][代码]'good'[代码][代码] [代码][代码]token= res.accesstoken[代码][代码] [代码][代码]}[代码][代码]else[代码][代码]{[代码][代码] [代码][代码]//token太久了,需要更新[代码][代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码][代码]const t = await getAccessToken()[代码][代码] [代码][代码]token = t[代码][代码] [代码][代码]//更新数据库中的token[代码][代码] [代码][代码]await mysql([代码][代码]"systemSet"[代码][代码]).update({[代码][代码] [代码][代码]accesstoken: token[代码][代码] [代码][代码]}).where({ id })[代码][代码] [代码][代码]msg = [代码][代码]'update'[代码][代码] [代码][代码]console.log(t.substring(0, 5) + [代码][代码]'...done!\r\n'[代码][代码])[代码][代码] [代码][代码]} [代码][代码]catch[代码] [代码](err) {[代码][代码] [代码][代码]console.log(err);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码] [代码][代码]//获取二维码数据[代码][代码] [代码][代码]if[代码] [代码](msg == [代码][代码]'good'[代码] [代码]|| msg == [代码][代码]'update'[代码][代码]) {[代码][代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码][代码]var[代码] [代码]q = await getqrc(token)[代码][代码] [代码][代码]// console.log(q)[代码][代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码][代码]// 存储到cos,并返回地址[代码][代码] [代码][代码]var[代码] [代码]p = await uploadimg(q)[代码][代码] [代码][代码]urlpath = [代码][代码]'https://'[代码] [代码]+ p.Location[代码][代码] [代码][代码]// console.log(p)[代码][代码] [代码][代码]} [代码][代码]catch[代码] [代码](err) {[代码][代码] [代码][代码]console.log(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]catch[代码] [代码](err) {[代码][代码] [代码][代码]var[代码] [代码]q = err[代码][代码] [代码][代码]console.log(err);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码]else[代码][代码]{[代码][代码] [代码][代码]var[代码] [代码]q = [代码][代码]'notoken'[代码][代码] [代码][代码]}[代码][代码] [代码][代码]//将地址返回到前端[代码][代码] [代码][代码]ctx.state.data = urlpath[代码][代码] [代码][代码]console.log([代码][代码]'finish\r\n'[代码][代码])[代码][代码]}[代码]小程序端代码(只贴获取二维码图片了): [代码]getqrcode:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]let that = [代码][代码]this[代码][代码] [代码][代码]wx.showLoading({[代码][代码] [代码][代码]title: [代码][代码]'图片正在生成中...'[代码][代码] [代码][代码]})[代码][代码] [代码][代码]qcloud.request({[代码][代码] [代码][代码]url: `${config.service.host}/weapp/getqrcode`,[代码][代码] [代码][代码]login: [代码][代码]false[代码][代码],[代码][代码] [代码][代码]data: { [代码][代码] [代码][代码]pptid: that.data.pptid,[代码][代码] [代码][代码]path:[代码][代码]'pages/detail/detail'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]success(result) {[代码][代码] [代码][代码]//成功获取到了地址[代码][代码] [代码][代码]console.log([代码][代码]'Success get qrcode:'[代码][代码],result)[代码][代码] [代码][代码]let res = result.data.data[代码][代码] [代码][代码]// var base64 = wx.arrayBufferToBase64(res.data); [代码][代码] [代码][代码]// console.log(res)[代码][代码] [代码][代码]// that.setData({ imgurlqr: "data:image/png;base64," + base64 })[代码][代码] [代码][代码]//从cos下载图片[代码][代码] [代码][代码]wx.downloadFile({[代码][代码] [代码][代码]url: res,[代码][代码] [代码][代码]success: [代码][代码]function[代码] [代码](res0) {[代码][代码] [代码][代码]//获取到临时地址,进行canvas的绘制[代码][代码] [代码][代码]that.drawCanv(res0.tempFilePath)[代码][代码] [代码][代码]},[代码][代码] [代码][代码]fail: [代码][代码]function[代码] [代码](err) {[代码][代码] [代码][代码]console.log(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码][代码] [代码][代码]fail(error) {[代码][代码] [代码][代码]util.showModel([代码][代码]'请求失败'[代码][代码], error);[代码][代码] [代码][代码]console.log([代码][代码]'request fail'[代码][代码], error);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码]基本就这些了吧。心累。先这样用用看吧,不知道会不会有其他问题=^=
2018-09-22 - 每周社区 | 上周社区问题反馈以及功能优化更新
各位微信开发者: 大家下午好。从本周开始,我们每周收到的 问题反馈、处理进度、社区功能更新 将以公告形式同步给大家,希望和大家一同打造小程序的生态。 一、上周问题反馈和处理进度(06.25-06.29) (一)修复中的问题: 关于微信小程序设置过多监听事件后报错 查看反馈 @font-face引入新字体,仅在IOS下且为外部字体时生效。查看反馈 查看反馈 cover-view 出现 background 无法覆盖 padding 区域 查看详情 iOS下,切换input组件,键盘会重新推出且位置计算有误 查看详情 意见反馈日志读写太频繁 查看详情 安卓全屏模式下,web-view没有占满屏幕,底部有部分留白 查看详情 工具中showToast icon='none'时无法触摸穿透 查看详情 iOS页面中多个video标签,其中一个全屏退出后video展示异常 查看详情 安卓光标移动后无法移回最后 查看详情 chooseLocation 工具上偶现只返回了经纬度 查看详情 开发工具将窗体独立之后,页面刷新/切换后不渲染 查看详情 wx.pageScrollTo导致fixed元素闪动 查看详情 小程序插件页面无法配置导航栏样式 查看详情 工具上分包预下载接口分包大小计算有误 查看详情 setBackgroundColor报无权限 接口上线后未放开权限位 查看详情 工具全局查找的查找范围不包含wxs文件 查看详情 iOS下 textarea的padding无法置0 查看详情 (二)修复完成,待客户端版本上线 安卓textarea adjust-position 设置失效 查看详情 statusBarHeight安卓返回0 查看详情 安卓text长按复制失效 查看详情 (三)已修复的问题 touch事件手机上timestamp位置不一致 查看详情 (四)需求反馈 webview 业务域名 优化 查看详情 支持自定义下拉刷新圆点的位置 查看详情 开发工具素材管理支持svg 查看详情 待讨论评估支持需求: 提供微信版本和公共库版本的对应关系数据,其他类似反馈 查看详情 uploadFile支持多文件上传 查看详情 (二)近期社区能力更新 我们在社区收到了大家希望增加”关注问题“、”收藏“帖子和通知消息回答定位的需求,近期已新增优化如下能力: 1. 增加“关注问题”的能力,关注有消息变动,会收到消息通知 2. 所有帖子新增“收藏”功能 3. 在个人主页上有所体现关注和收藏功能 4. 优化消息通知回答定位 [图片] [图片] 三、《诚邀开发者共同营造社区》的反馈 1.关于社区的官方回答 我们每天都有安排相关的同学值班回复处理大家的问题。 但是我们希望通过这些改造,促进开发者们之间的交流, 问题和回答在大家思维碰撞中产生火花。而bug和需求类,由我们来处理。 [图片] 2.关于通知中心的反馈 已优化上线该问题,可以具体定位到该问题的回答、该帖子的评论。 [图片] 3.关于已知问题 关于已知问题,大家可以关注社区“已知问题”模块,看我们的已知问题和相关修复。 [图片] 4.关于社区的评论bug 大家反馈符号超过了编辑框,帮助我们发现了一个bug,我们已经修复了这个问题。谢谢大家的反馈。 [图片] 5.关于交流群 我们希望所有的交流可以在社区完成,方便更多的开发者看见这类问题。 [图片] 感谢大家的反馈,也希望大家和我们一起共同营造社区的良好氛围。 微信团队 2018.07.03
2018-07-03