- 使用postman访问不通?
使用Postman访问v3接口返回 There was an error in evaluating the Pre-request Script:Error: Invalid PEM formatted message.
2022-11-10 - 微信支付v3 node sdk
ape-node-wechatpay-v3 如果您觉得这个SDK有帮助,请帮忙在 GitHub项目主页 点个赞,或者您对这个SDK有任何问题或建议,欢迎联系我。 [图片] 目录 安装 npm 用法介绍 创建 weChatPay 实例 获取签名值 获取 HTTP Authorization 头 下载微信支付平台证书 支付回调签名验证 证书、回调解密 内置方法介绍 Jsapi 下单 Native 下单 版本介绍 安装 npm [代码]npm i ape-node-wechatpay-v3 --save [代码] weChatPay 用法介绍 创建 weChatPay 实例 创建实例 [代码]const apeWeChatPay = require("ape-node-wechatpay-v3"); const fs = require("fs"); const weChatPay = new apeWeChatPay({ appid: "xxxxxxxxxx", mchid: "xxxxxxxxxx", serial_no: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", authType: "WECHATPAY2-SHA256-RSA2048", apiclientCert: fs.readFileSync("xxx/certificate/apiclient_cert.pem"), apiclientkey: fs.readFileSync("xxx/certificate/apiclient_key.pem"), certPath: "xxx/certificate", APIv3: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", }); [代码] 参数说明 参数名称 参数介绍 是否必须 appid 公众号(移动应用 / 小程序)的 appid 否 mchid 商户号 是 serial_no 证书序列号 是 authType 请求头签名认证类型,不传则默认为 WECHATPAY2-SHA256-RSA2048 否 apiclientCert 公钥 是 apiclientkey 密钥 是 certPath 微信支付平台证书下载路径(需要回调解密以及验证签名则必填)【包含 wechatpay.pem 和 wechatpaySerial.txt 俩个文件】,建议与公钥密钥在一个文件夹内 否 APIv3 商户后台配置的 APIv3 密钥(需要回调解密以及验证签名则必填) 否 userAgent 微信支付平台证书下载接口请求头 user-agent(默认为 node Serve) 否 [代码]注意:serial_no 是证书序列号,请在商户后台查看。[代码] 获取签名值 示例代码 [代码]let signature = weChatPay.getSignature(method, url, timestamp, nonce_str, body); [代码] 参数说明 参数名称 参数介绍 是否必须 [代码]method[代码] 支付接口方法 是 [代码]url[代码] 支付接口地址 是 [代码]timestamp[代码] 时间戳 是 [代码]nonce_str[代码] 请求随机串 是 [代码]body[代码] 请求体(接口需要传则给,反之给空字符串,如"") 否 获取 HTTP Authorization 头 示例代码 [代码]let authorization = weChatPay.getAuthorization(nonce_str, timestamp, signature); [代码] 参数说明 参数名称 参数介绍 是否必须 [代码]nonce_str[代码] 请求随机串(需要与获取签名值的一致) 是 [代码]timestamp[代码] 时间戳 是 [代码]signature[代码] 签名值 是 下载微信支付平台证书 会生成 wechatpay.pem 和 wechatpaySerial.txt 俩个文件,每次更新会覆盖旧的。 [代码]微信官方建议在十二小时内更新,可以写定时任务[代码] 示例代码 [代码]let result = {}; try { result = await weChatPay.getWeChatPayCert(); } catch (error) { console.log(error); } console.log(result); // { code: 200, msg: '下载证书成功' } [代码] [代码]!!!使用此方法必须在构造实体类时传递 certPath 、 APIv3 这俩个参数 !!![代码] 文件说明 文件名称 说明 wechatpay.pem 下载平台证书接口解密出来公钥(回调签名验证需要) wechatpaySerial.txt 下载平台证书接口的公钥序列号(回调签名验证需要) 返回说明(为了方便开发者做对应的处理) code data msg 200 下载成功 解释说明 500 / 其他(在 catch 里返回) 下载失败(错误信息),详细请看 msg 解释说明 支付回调签名验证 示例代码 [代码]let Body = req.body; // 微信请求你的回调接口的实体类 body let Headers = req.headers; // 微信请求你的回调接口的 headers let result = null; try { result = await weChatPay.verifySignature( Headers["wechatpay-signature"], // 应答签名 Headers["wechatpay-serial"], // 平台证书序列号 Headers["wechatpay-timestamp"], // 生成签名的时间戳 Headers["wechatpay-nonce"], // 应答随机串 req.body ); } catch (error) { console.log(error); } console.log(result); // { code: 200, msg: '验签名串和签名验证通过' } [代码] [代码]!!!使用此方法必须在构造实体类时传递 certPath 、 APIv3 这俩个参数 !!![代码] 返回说明(为了方便开发者做对应的处理) code data msg 200 验证通过 解释说明 500 / 其他(在 catch 里返回) 验证失败(错误信息),详细请看 msg 解释说明 回调解密 建议在签名验证成功后解密 示例代码 [代码]let Body = req.body; // 微信请求你的回调接口的实体类 body let Headers = req.headers; // 微信请求你的回调接口的 headers let data = null; try { data = await weChatPay.decrypting( Body.resource.ciphertext, // 结果数据密文 Body.resource.nonce, // 加密使用的随机串 Body.resource.associated_data // 附加数据 ); } catch (error) { console.log(error); } console.log(data); // { code: 200, data: xxxx(解密的数据), msg: '解密成功' } [代码] [代码]!!!使用此方法必须在构造实体类时传递 certPath 、 APIv3 这俩个参数 !!![代码] 返回说明(为了方便开发者做对应的处理) code data msg 200 解密出来的数据 解释说明 500 / 其他(在 catch 里返回) 解密失败(错误信息),详细请看 msg 解释说明 weChatPay 内置方法介绍 Jsapi 下单 示例代码 [代码]let data = { appid: "xxxxxx", mchid: "xxxxxx", description: "测试", out_trade_no: "xxxxxx", notify_url: "xxxxxx", amount: { total: 1, }, payer: { openid: "xxxxxx", }, }; let result; try { result = await weChatPay.jsapiPay(data); } catch (error) { console.log(error); } console.log(result); // 正常返回如下示例 // { // "code": 200, // "prepay_id": "xxxxxxxxxx" // } [代码] 参数说明 参数名称 参数介绍 是否必须 [代码]data[代码] jsapi 下单的 body 参数 是 详细请点击查看微信官方-jsapi-下单文档 Native 下单 示例代码 [代码]let data = { appid: "xxxxxx", mchid: "xxxxxx", description: "测试", out_trade_no: "xxxxxx", notify_url: "xxxxxx", amount: { total: 1, }, }; let result; try { result = await weChatPay.nativePay(data); } catch (error) { console.log(error); } console.log(result); // 正常返回如下示例 // { // "code": 200, // "code_url": "xxxxxxxxxx" // } [代码] 参数说明 参数名称 参数介绍 是否必须 [代码]data[代码] native 下单的 body 参数 是 详细请点击查看微信官方-native-下单文档 版本介绍 版本号 版本介绍 1.0.0 仅支持获取签名和获取头部参数 Authorization 1.0.1 在 1.0.0 的基础上增加了说明文档 1.0.2 增加了 native 支付下单函数的封装 1.0.21 增加了 jsapi 支付下单函数的封装 1.0.3 增加了微信支付平台证书下载方法、支付回调签名验证的方法、 证书(回调)解密方法的封装
2023-12-08 - 官方提供的隐私弹窗不好使?
想试下官方提供的隐私授权弹窗,为啥不好使,直接报错,也没有弹窗,前提是清完缓存,并没有拒绝过授权 [图片][图片]
2023-09-15 - 适配隐私保护协议接口
1、相关api说明 适配过程中主要是用到了以下几个接口 1. wx.onNeedPrivacyAuthorization:用于监听隐私接口需要用户授权事件,只有当隐私协议需要用户授权时才会由平台触发此事件,然后开发者需要弹窗显示隐私协议说明。如果用户拒绝授权,则隐私接口调用失败,在下次调用到隐私接口时还会继续弹。 2. wx.requirePrivacyAuthorize:用于模拟隐私接口调用,并触发隐私弹窗逻辑,也就是会触发wx.onNeedPrivacyAuthorization,但如果用户之前已经同意过隐私授权,会立即返回success回调,不会触发 wx.onNeedPrivacyAuthorization 。这个api的使用场景目前我是用在获取用户昵称时,在下一篇文章会进行说明。 3. wx.openPrivacyContract:用于打开隐私协议页面。 其他api大家可以根据具体情况选择使用。 2、弹窗思路 其实就是写一个自定义组件,然后在有调用到隐私接口的页面引入,利用自定义组件的attached方法,把各个页面的隐私弹窗组件的显示、隐藏方法保存到自定义组件的全局变量中,当用户点击隐私协议弹窗的同意、拒绝按钮时调用resolve方法,将对应的参数通知给平台。 3、注意点 隐私接口在隐私保护指引中有声明才能调用基础库版本2.32.3及以上开始支持2023.9.15号之前,在 app.json 中配置 __usePrivacyCheck__: true 后,会启用隐私相关功能,如果不配置或者配置为 false 则不会启用。2023.9.15号之后,不论 app.json 中是否有配置 __usePrivacyCheck__,隐私相关功能都会启用wx.onNeedPrivacyAuthorization 是覆盖式注册监听,若重复注册监听,则只有最后一次注册会生效同意授权后如果想取消授权,在开发工具上 清缓存->清除模拟器缓存->清除授权数据,在手机上删除小程序即可隐私弹窗的z-index要设置成最大,避免被其他遮罩挡住导致无法点击,另外如果有调到官方的api例如wx.showLoading,也要注意是否设置了mask,避免在loading的时候弹出隐私弹窗,导致弹窗无法点击,而loading又要等弹窗关闭才会消失的尴尬情况4、代码如下 4.1、app.json "usingComponents": { //全局自定义组件 "privacyPopup": "/components/privacy/privacyPopup" }, //开启隐私相关功能 "__usePrivacyCheck__": true 4.2、自定义组件privacyPopup 4.2.1、privacyPopup.wxml <view class="container" wx:if="{{show}}"> <view class="cover {{showCoverAnimation?'cover-fade-in':''}}" catch:touchmove="return"></view> <view class="privacy-box {{showBoxAnimation?'slade-in':''}} {{device.isPhoneX? 'phx_68':''}}" catch:touchmove="return"> <view class="title flex-start-horizontal"> <view class="logo" wx:if="{{privacyConfig.icon}}"> <image class="icon" src="{{privacyConfig.icon}}"></image> </view> <view class="mini-name">{{privacyConfig.name || '小程序'}}</view> </view> <view class="tips"> <view class="privacy-content"> <view class="start">{{privacyConfig.content.start}}</view> <view class="link" bindtap="openPrivacyContract"> {{privacyConfig.content.mid}} </view> <view class="end">{{privacyConfig.content.end}}</view> </view> </view> <view class="buttons flex-center"> <button class="cancel reset-btn" bindtap="disagree">拒绝</button> <button class="save reset-btn" id="agree-btn" open-type="agreePrivacyAuthorization" bindagreeprivacyauthorization="agree">同意</button> </view> </view> </view> 4.2.2、privacyPopup.wxss @import "/app.wxss"; .cover{ background-color: #111; opacity: 0; position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: 50000; transition: opacity .3s; } .privacy-box{ position: fixed; left: 0rpx; right: 0rpx; bottom: 0rpx; z-index: 51000; background-color: #fff; box-sizing: border-box; padding-top: 60rpx; padding-left: 50rpx; padding-right: 50rpx; border-radius: 30rpx 30rpx 0 0; transform: translateY(100%); transition: transform .3s, bottom .3s; } .cover-fade-in{ opacity: 0.7; } .slade-in{ transform: translateY(0); bottom: 0rpx; } .privacy-box .title{ font-size: 32rpx; font-weight: 500; text-align: center; margin-bottom: 40rpx; color: #161616; } .privacy-box .title .icon{ width: 46rpx; height: 46rpx; margin-right: 8rpx; vertical-align: bottom; border-radius: 50%; } .privacy-box .title .mini-name{ /*margin-bottom: 2rpx;*/ } .privacy-box .tips{ margin-bottom: 20rpx; } .privacy-box .tips .privacy-content{ color: #606266; font-size: 30rpx; margin-bottom: 20rpx; line-height: 1.6; } .privacy-box .tips .privacy-content .start, .privacy-box .tips .privacy-content .link, .privacy-box .tips .privacy-content .end { display: inline; } .privacy-box .tips .privacy-content .link{ color: #1890FF; } .privacy-box .buttons{ margin-bottom: 40rpx; margin-top: 50rpx; text-align: center; font-size: 34rpx; font-weight: 550; } .privacy-box .buttons .save{ width: 220rpx !important; height: 90rpx; line-height: 90rpx; border-radius: 14rpx; color: #fff; /*background:#fff linear-gradient(90deg,rgba(246, 120, 121,.9) 10%, rgb(246, 120, 121));*/ background-color: #07c160; margin-left: -50rpx; } .privacy-box .buttons .cancel{ width: 220rpx !important; height: 90rpx; line-height: 90rpx; border-radius: 14rpx; color: #07c160; background-color: #F2F2F2; } 4.2.3、privacyPopup.js //获取应用实例 const tabbar = require("../../utils/tabbar.js"); const app = getApp(); //用来保存各个页面注册的隐私协议回调事件 let privacyHooks = {}; if (wx.onNeedPrivacyAuthorization) { console.warn("当前基础库支持api wx.onNeedPrivacyAuthorization"); wx.onNeedPrivacyAuthorization(resolve => { console.warn("需要隐私协议弹窗:", resolve); const pages = getCurrentPages(); const route = pages[pages.length - 1].route; const hook = privacyHooks[route]; if (hook) { hook.resolve = resolve; hook.show(resolve); } else { console.error("当前页面没有注册隐藏协议弹窗回调:", route); } }) } else { console.warn("当前基础库不支持api wx.onNeedPrivacyAuthorization"); } Component({ data: { show: false, showCoverAnimation: false,//显示类别选择窗口动画 showBoxAnimation: false,//显示类别选择窗口动画 }, lifetimes: { //在组件实例进入页面节点树时执行 attached: function () { console.warn("privacyPopup attached"); const pages = getCurrentPages(); privacyHooks[pages[pages.length - 1].route] = { show: resolve => { this.show(resolve); }, close: () => { this.hide(); } } }, //在组件实例被从页面节点树移除时执行 detached: function () { console.warn("privacyPopup detached"); } }, methods: { /** * 同意隐私协议 * @param e */ agree(e) { Object.values(privacyHooks).forEach(hook => { hook.close(); hook.resolve && hook.resolve({ event: "agree", buttonId: "agree-btn" }); }); }, /** * 不同意隐私协议 * @param e */ disagree(e) { Object.values(privacyHooks).forEach(hook => { hook.close(); hook.resolve && hook.resolve({ event: "disagree" }); }); }, /** * 显示隐私协议授权弹窗 */ show(resolve) { app.fillConfig(this, ["privacyConfig"], data => { console.log("显示隐私协议弹窗"); const device = app.getSystemInfo(); this.setData({ show: true, device, }, () => { this.setData({ showCoverAnimation: true, showBoxAnimation: true }); //自定义隐私弹窗曝光告知平台 resolve({event: "exposureAuthorization"}); }); tabbar.hideTabByPrivacy(this); }); }, /** * 关闭隐私协议授权弹窗 */ hide() { console.log("隐藏隐私协议弹窗"); this.setData({ showCoverAnimation: false, showBoxAnimation: false }, () => { const that = this; setTimeout(function () { that.setData({ show: false }) }, 300) }) tabbar.showTabByPrivacy(this); }, /** * 打开隐私协议 */ openPrivacyContract() { wx.openPrivacyContract({ success: res => { console.log("openPrivacyContract success") }, fail: res => { console.error("openPrivacyContract fail", res) } }) } } }) 5、弹窗效果 [图片]
2023-09-13 - xr-frame中渲染透明视频
渲染透明视频可以通过将透明视频通道转换为灰度图的方式,利用uv将透明叠加到视频上。shader处理如下: vec4 color = texture2D(u_baseColorMap, vec2(vTextureCoord.x*0.5,vTextureCoord.y)); vec4 colora = texture2D(u_baseColorMap, vec2(vTextureCoord.x*0.5 + 0.5,vTextureCoord.y)); vec4 baseColor = vec4(color.xyz,colora.x); gl_FragData[0] = baseColor; 具体代码: const xrFrameSystem = wx.getXrFrameSystem(); function createVideoTsbsEffect(scene) { return scene.createEffect( { "name": "video-tsbs", "properties": [ { "key": "u_baseColorFactor", "type": 3, "default": [1, 1, 1, 0] } ], "images": [{ "key": "u_baseColorMap", "default": "white", "macro": "WX_USE_BASECOLORMAP" }], "defaultRenderQueue": 3000, "passes": [ { "renderStates": { "cullOn": true, "blendOn": true, "depthWrite": true, "cullFace": 2 }, "lightMode": "ForwardBase", "useMaterialRenderStates": true, "shaders": [0, 1] }], "shaders": [ `#version 100 precision highp float; attribute vec3 a_position; attribute vec2 a_texCoord; uniform mat4 u_projection; uniform mat4 u_world; uniform mat4 u_view; varying highp vec2 vTextureCoord; void main() { vTextureCoord = a_texCoord; gl_Position = u_projection * u_view * u_world * vec4(a_position,1.0); }`, `#version 100 precision highp float; uniform highp vec4 u_baseColorFactor; #ifdef WX_USE_BASECOLORMAP uniform sampler2D u_baseColorMap; #endif varying highp vec2 vTextureCoord; void main() { #ifdef WX_USE_BASECOLORMAP vec4 color = texture2D(u_baseColorMap, vec2(vTextureCoord.x*0.5,vTextureCoord.y)); vec4 colora = texture2D(u_baseColorMap, vec2(vTextureCoord.x*0.5 + 0.5,vTextureCoord.y)); vec4 baseColor = vec4(color.xyz,colora.x); #else vec4 baseColor = u_baseColorFactor; #endif gl_FragData[0] = baseColor; } `] } ) } xrFrameSystem.registerEffect("video-tsbs", createVideoTsbsEffect); wxml: <xr-scene bind:ready="handleReady"> <xr-assets> <xr-asset-load type="texture" asset-id="waifu" src="https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/demo/waifu.png" /> <xr-asset-load type="video-texture" asset-id="test" src="https://demo.uality.cn/output-lr.mp4" options="autoPlay:true,loop:true" /> <xr-asset-material asset-id="video-tsbs-mat" effect="video-tsbs" uniforms="u_baseColorMap:video-test" /> </xr-assets> <xr-mesh node-id="cube" geometry="cube" material="video-tsbs-mat" /> <xr-camera clear-color="0.4 0.8 0.6 1" position="0 1 4" target="cube" camera-orbit-control /> </xr-scene> 视频生成: 通过ffmpeg生成将带有透明通道的mov格式视频转换为左(rgb通道信息)右(alpha通道信息)的MP4视频。 ffmpeg -i input.mov -vf "split [a], pad=iw*2:ih [b], [a] alphaextract, [b] overlay=w" -y output-lr.mp4 也可以用百度小程序“AFX透明视频”带的工具制作。(左右的话,直接修改shader的偏移就可以了) 转换出来的视频结果如下图: [图片]
2023-04-07 - We分析 · 小程序数据分析平台
内含平台介绍、接入指引及系列课程,帮助您快速了解、上手平台,精细化分析小程序数据,驱动业务决策。
2022-11-21 - 快速上手微信云托管
微信云托管是为开发者提供的后端服务云原生解决方案,支持托管任意语言及框架的容器化应用,创建环境即可享受能自动扩缩容的容器资源,用户可面向代码/镜像等方式使用,免服务器、免运维。
2022-07-29 - 微信十年的产品思考
[视频] 微信十年,你和微信又有哪些故事呢? 欢迎在评论区分享交流。
2021-09-22