- 关于禁止小程序JavaScript解释器使用规范要求
为进一步提升小程序的安全性和用户体验,目前平台对提审的小程序均需进行安全检测,在检测过程中,发现有小程序采用内置 JavaScript 解释器(如eval5、estime、evil-eval等)的方式,动态执行JS代码、对小程序wxml代码进行热更新。对于使用解释器的小程序,平台将自2022年7月6日开始在代码审核环节进行驳回,请各位开发者于7月6日前完成自查、修复。 具体违规案例 一、动态下发代码执行 某小程序引入JS解释器模块,在预埋场景下触发动态执行代码的逻辑,从而从服务端后台拉取要动态执行的代码或字段,在JS解释器中动态执行代码; [图片] 二、小程序页面文件热更新 下面这个例子为某小程序引入 JS 解释器模块执行小程序热更新; [图片] 三、其他情况 部分数值计算类小程序会引入解释器来执行数学表达式运算功能,对于数值计算,请使用其他方式,不得使用解释器提供的动态eval代码执行能力实现; [图片] 四、修复指引 若小程序在代码提审阶段因存在解释器被要求整改,请根据代码提审反馈,自查相应的文件,在删除相应的解释器文件后重新提交代码审核; 其他常见问题 Q1: 小程序中解释器文件是第三方包依赖引入的,这种如何处理? A1: 平台不允许开发者使用JS解释器来动态执行代码,若小程序代码中存在JS解释器逻辑,请根据小程序审核驳回细节自行移除或联系依赖提供者、服务商移除后再次提交审核; Q2: 经过自查后提交代码仍提示存在解释器,这种如何处理? A2: 请确保提交的小程序代码中不存在解释器文件以及JavaScript 代码解析模块非正常使用,若仍存在问题,请提交客服复查。
2022-06-23 - 云函数V3支付全家桶
// 云函数入口文件 var cloud = require('wx-server-sdk') crypto = require('crypto') request = require('request') NodeRSA = require('node-rsa')//npm安装 Form = require('./Form.class') //Form 源自https://https://developers.weixin.qq.com/community/develop/article/doc/000c24f0390ff8b5d91b2489059413 //再次感谢社区北望大佬 cloud.init({ env: 'xxxxxx' }) const db = cloud.database() // 云函数入口函数 exports.main = async (event, context) => { const rq = options => new Promise((resolve, reject) => {...}) // 基本参数 var facilitator = event.facilitator=='undefined'?false:event.facilitator//facilitator=true时为服务商模式,false为直连模式 console.log(`${facilitator?'服务商':'直连'}模式`) var mchid = facilitator?'160xxxxx92':'16xxxxx84'//服务商or直连商户号 var appId = 'wx41xxxxxxfd6' //服务商和直连商户的appId var APIv3Key = facilitator?'xxxxxxxxx':'xxxxxxxxxx'//服务商or直连商户APIv3密钥 var cert_Doc = facilitator?'certificates':'direct_certificates' //存在数据库的证书记录的_id var timeStamp = parseInt(Date.now()/1000) var nonce_str = Math.random().toString(36).substr(2, 13) var url = 'https://api.mch.weixin.qq.com'+event.url //证书与私钥 var certificates = (await db.collection('a-data').doc(cert_Doc).get()).data //存在数据库的证书 var {serial_no} = certificates var {wx_serial_no} = certificates // 敏感信息加密 var RSAoaep = (e)=>{...} // getHeaders const getHeaders = (method,meta)=>{...} // JSAPI支付 if(event.url.indexOf('/transactions/jsapi')>-1){ var {data} = event if(facilitator){//服务商模式 data.sp_mchid = mchid data.sp_appid = appId }else{//直连模式 data.mchid = mchid data.appid = appId } ..... return { payment,//前端由此唤起支付 errMsg:'下单成功', errCode:0 } } // 申请退款 if(event.url=='/v3/refund/domestic/refunds'){ .... } // 特约商户进件:提交申请单 if(event.url=='/v3/applyment4sub/applyment/'){ .... } // 特约商户进件:图片上传接口 if(event.url=='/v3/merchant/media/upload'){ ..... } // 特约商户进件:查询申请单状态 if(event.url.indexOf('/v3/applyment4sub/applyment/business_code')>-1||event.url.indexOf('/v3/applyment4sub/applyment/applyment_id')>-1){ ...... } // 检测微信支付平台证书是否更新,该方法应定期且间隔不超过12小时执行一次,我是在更新公众号access_token的方法中调用,1h一次 if(event.url=='/v3/certificates'){ ...... } /*更新平台证书方法(由于云函数nodejs版本过低,该方法请本地运行): if(event.url=='/v3/certificates'){ .... } */ } 感兴趣请私信交流
2021-08-28 - 微信支付APIv3图片、视频上传类接口nodejs纯语言无依赖版参考实现
受官方技术助手伙伴在某次问答亲测有效代码启发,以下ES2015代码为v3上传文件文档补充实现而做,供其他开发语言参考实现。 上传媒体文件API的是基于 RFC2388 协议,圈定的范围是,有大小限制的图片(2M)或者视频(5M)类型单文件,按API接口标准,需要对文件的meta{filename,sha256}信息做数据签名,并且这个meta还须随请求载核一同发送给服务端,难就难在这里了,唉嘘~ 官方文档小分队犯了一个错误,就是试图用文本语言来表达非字符内容,整得一众开发者迷途了,社区反馈波澜滔滔。这支小分队应该每人扣一个长鹅抱宠,捐给像俺这样努力帮扶开发者的贡献者(😄)。其实rfc2388标准上有写,这个协议就是扩展HTTP文本协议,用来传输非字符内容的,引述如下: multipart/form-data can be used for forms that are presented using representations other than HTML (spreadsheets, Portable Document Format, etc), and for transport using other means than electronic mail or HTTP. This document defines the representation of form values independently of the application for which it is used. 上述引述内容,提到3种文件,HTML文件还算是字符型文件,表格及PDF文件就已经算是二进制文件了,文件内容人类得借助专用软件翻译,才能转成可被识别内容(肉眼能直接识别的是大牛,不再此列)。受官方技术助手伙伴在某次问答亲测有效代码截图启示,特意又研读了几遍RFC协议,国庆档给抽出成无依赖ES2015版本,已内置于另一款著名支付产品SDK包中,亲测可用,以下版本是微信支付社区特供,单文件、无依赖,适合云开发集成使用。 废话说了一箩筐,还是上代码吧: [代码]const {extname} = require('path') /** * Simple and lite of `multipart/form-data` implementation, most similar to `form-data` * * ```js * (new Form) * .append('a', 1) * .append('b', '2') * .append('c', Buffer.from('31')) * .append('d', JSON.stringify({}), 'any.json') * .append('e', require('fs').readFileSync('/path/your/file.jpg'), 'file.jpg') * .getBuffer() * ``` */ class Form { /** * Create a `multipart/form-data` buffer container for the file uploading. * * @constructor */ constructor() { Object.defineProperties(this, { /** * built-in mime-type mapping * @type {Object<string,string>} */ mimeTypes: { value: { bmp: `image/bmp`, gif: `image/gif`, png: `image/png`, jpg: `image/jpeg`, jpe: `image/jpeg`, jpeg: `image/jpeg`, mp4: `video/mp4`, mpeg: `video/mpeg`, json: `application/json`, }, configurable: false, enumerable: false, writable: true, }, /** * @type {Buffer} */ dashDash: { value: Buffer.from(`--`), configurable: false, enumerable: false, writable: false, }, /** * @type {Buffer} */ boundary: { value: Buffer.from(`${`-`.repeat(26)}${`0`.repeat(24).replace(/0/g, () => Math.random()*10|0)}`), configurable: false, enumerable: false, writable: false, }, /** * @type {Buffer} */ CRLF: { value: Buffer.from(`\r\n`), configurable: false, enumerable: false, writable: false, }, /** * The Form's data storage * @type {array<Buffer>} */ data: { value: [], configurable: false, enumerable: true, writable: true, }, /** * The entities' value indices whose were in `this.data` * @type {Object<string, number>} */ indices: { value: {}, configurable: false, enumerable: true, writable: true, }, }) } /** * To retrieve the `data` buffer * * @return {Buffer} - The payload buffer */ getBuffer() { return Buffer.concat([ this.dashDash, this.boundary, this.CRLF, ...this.data.slice(0, -2), this.boundary, this.dashDash, this.CRLF, ]) } /** * To retrieve the `Content-Type` multipart/form-data header * * @return {Object<string, string>} - The `Content-Type` header With `this.boundary` */ getHeaders() { return { 'Content-Type': `multipart/form-data; boundary=${this.boundary}` } } /** * Append a customized mime-type(s) * * @param {Object<string,string>} things - The mime-type * * @return {Form} - The `Form` class instance self */ appendMimeTypes(things) { Object.assign(this.mimeTypes, things) return this } /** * Append data wrapped by `boundary` * * @param {string} field - The field * @param {string|Buffer} value - The value * @param {String} [filename] - Optional filename, when provided, then append the `Content-Type` after of the `Content-Disposition` * * @return {Form} - The `Form` class instance self */ append(field, value, filename = '') { const {data, dashDash, boundary, CRLF, mimeTypes, indices} = this data.push(Buffer.from(`Content-Disposition: form-data; name="${field}"${filename && Buffer.isBuffer(value) ? `; filename="${filename}"` : ``}`)) data.push(CRLF) if (filename || Buffer.isBuffer(value)) { data.push(Buffer.from(`Content-Type: ${mimeTypes[extname(filename).substring(1).toLowerCase()] || `application/octet-stream`}`)) data.push(CRLF) } data.push(CRLF) indices[field] = data.push(Buffer.isBuffer(value) ? value : Buffer.from(String(value))) data.push(CRLF) data.push(dashDash) data.push(boundary) data.push(CRLF) return this } } module.exports = Form module.exports.default = Form [代码] 测试用例如下: [代码]lib/form ✓ should be class `Form` new Form ✓ should instanceOf Form and have properties `data` and `indices` ✓ The `mimeTypes` property should be there and only allowed append(cannot deleted) ✓ The `dashDash` Buffer property should be there and cannot be deleted/modified ✓ The `boundary` Buffer property should be there and cannot be deleted/modified ✓ The `CRLF` Buffer property should be there and cannot be deleted/modified ✓ The `data` property should be instanceOf Array and cannot deleted ✓ The `indices` property should be instanceOf Object and cannot deleted ✓ Method `getBuffer()` should returns a Buffer instance and had fixed length(108) default ✓ Method `getHeaders()` should returns a Object[`Content-type`] with `multipart/form-data; boundary=` ✓ Method `appendMimeTypes()` should returns the Form instance ✓ Method `appendMimeTypes({any: 'mock'})` should returns the Form instance, and affected `form.data` property ✓ Method `append()` should returns the Form instance, and affected `form.data` property ✓ Method `append()` should append name="undefined" disposition onto the `form.data` property ✓ Method `append({}, 1)` should append name="[object Object]" disposition onto the `form.data` property ✓ Method `append('meta', JSON.stringify({}), 'meta.json')` should append a `Content-Type: application/json` onto the `form.data` property ✓ Method `append('image_content', Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64'), 'demo.gif')` should append a `Content-Type: image/gif` onto the `form.data` property [代码] API文档如下 Simple and lite of [代码]multipart/form-data[代码] implementation, most similar to [代码]form-data[代码] [代码](new Form) .append('a', 1) .append('b', '2') .append('c', Buffer.from('31')) .append('d', JSON.stringify({}), 'any.json') .append('e', require('fs').readFileSync('/path/your/file.jpg'), 'file.jpg') .getBuffer() [代码] Form new Form() .getBuffer() ⇒ [代码]Buffer[代码] .getHeaders() ⇒ [代码]Object.<string, string>[代码] .appendMimeTypes(things) ⇒ [代码]Form[代码] .append(field, value, [filename]) ⇒ [代码]Form[代码] new Form() Create a [代码]multipart/form-data[代码] buffer container for the file uploading. form.getBuffer() ⇒ [代码]Buffer[代码] To retrieve the [代码]data[代码] buffer Kind: instance method of [代码]Form[代码] Returns: [代码]Buffer[代码] - - The payload buffer form.getHeaders() ⇒ [代码]Object.<string, string>[代码] To retrieve the [代码]Content-Type[代码] multipart/form-data header Kind: instance method of [代码]Form[代码] Returns: [代码]Object.<string, string>[代码] - - The [代码]Content-Type[代码] header With [代码]this.boundary[代码] form.appendMimeTypes(things) ⇒ [代码]Form[代码] Append a customized mime-type(s) Kind: instance method of [代码]Form[代码] Returns: [代码]Form[代码] - - The [代码]Form[代码] class instance self Param Type Description things [代码]Object.<string, string>[代码] The mime-type form.append(field, value, [filename]) ⇒ [代码]Form[代码] Append data wrapped by [代码]boundary[代码] Kind: instance method of [代码]Form[代码] Returns: [代码]Form[代码] - - The [代码]Form[代码] class instance self Param Type Description field [代码]string[代码] The field value [代码]string[代码] | [代码]Buffer[代码] The value [filename] [代码]String[代码] Optional filename, when provided, then append the [代码]Content-Type[代码] after of the [代码]Content-Disposition[代码] mimeTypes : [代码]Object.<string, string>[代码] built-in mime-type mapping Kind: global variable dashDash : [代码]Buffer[代码] Kind: global variable boundary : [代码]Buffer[代码] Kind: global variable CRLF : [代码]Buffer[代码] Kind: global variable data : [代码]array.<Buffer>[代码] The Form’s data storage Kind: global variable indices : [代码]Object.<string, number>[代码] The entities’ value indices whose were in [代码]this.data[代码] 此类内置常用的几种文件类型([代码]append[代码]第三入参以文件名后缀比对),已经够用了,视频仅内置了两种,对于 官方接口支持的[代码]avi[代码], [代码]wmv[代码], [代码]mov[代码], [代码]mkv[代码], [代码]flv[代码], [代码]f4v[代码], [代码]m4v[代码], [代码]rmvb[代码],开发者可用透过 [代码]appendMimeTypes[代码] 方法,自行扩展以符合 RFC2388 规范。 图片上传接口,用法 [代码]const form = new Form form.append( 'file', require('fs').readFileSync('/path/your/file.jpg'), 'file.jpg' ) .append( 'meta', JSON.stringify({ filename:'file.jpg', sha256:'779a563f99f824975b3651bfd8597555e69fb135925e460dae3996d47c415fb0' }), 'meta.json' ) [代码] 整个需要发送的表单体就准备妥当了,然后按照v3开发规范,该数据签名的签名,想用什么[代码]client[代码]提交就用什么[代码]client[代码],然后就没然后了。。。 以我习惯用的 [代码]axios[代码] 为例,数据提交类似如下: [代码]const axios = require('axios') //伪代码, baseURL 要按实际接口地址赋值, Authorization 要按v3规范赋值 axios.create({baseURL}).post( form.getBuffer(), {headers: { Authorization: 'WECHATPAY2-SHA256-RSA2048 ', Accept: 'application/json', ...form.getHeaders(), }} ) .catch(({response: {headers, data}}) => ({headers, data})) .then(({headers, data}) => ({headers, data})) .then(console.log) [代码] 写到最后 Form类文件以MIT开源,文章转载请注明出处「来自微信开发者社区」。
2020-10-24