- 什么小程序需要医疗相关类目?
什么内容或服务的小程序需要补充医疗相关类目? [图片] 1、 公立医疗机构:小程序涉及提供公立医疗机构提供的就医、健康咨询/问诊、医疗保健信息等服务。需补充医疗-公立医疗机构类目。 所需资质:《医疗机构执业许可证》和《事业单位法人证书》 案例:如图可看到,该小程序公立医疗机构是提供门诊挂号门诊缴费等就医服务,需补充医疗-公立医疗机构类目。 [图片] 2、私立医疗机构:小程序内涉及提供私立医疗机构提供的就医、健康咨询/问诊、医疗保健信息等服务。需补充医疗-私立医疗机构类目。 所需资质:《医疗机构执业许可证》 案例:如图可看到,该小程序是私立医疗机构,涉及提供医疗美容服务,需补充医疗-私立医疗机构类目。 [图片] 3、就医服务:小程序内涉及提供查询报告单、预约挂号、在线缴费等医疗服务,或提供医疗机构推荐服务。需补充医疗-就医服务类目。 所需资质(3选1): (1)卫健委(卫生健康委员会)批文或医管局批文; (2)签订协议的合作医院列表、与其中一家医院签订的协议样本、该协议医院的《医疗机构执业许可证》、小程序申请者的承诺书; (3)平台类型服务:《小程序开发者承诺函》、及其他平台良好经营情况的证明材料(如app store 评分超过50万人及app内提供该类目的服务内容截图) 案例:如图可看到,该小程序涉及提供多家门店预约康复,并且有档案绑定,精选医师涉及多家医院优秀医生的介绍,需补充医疗-就医服务类目。 [图片] 4、医疗-健康咨询服务:小程序内涉及提供医疗信息科普等服务内容包括医疗手册、养生类文章医疗资讯科普、小儿推拿等需补充医疗-健康咨询服务类目。 所需资质:《非经营性互联网信息服务备案核准》 案例:如图可看到,该小程序涉及提供养生类文章资讯信息服务,需补充医疗-健康咨询服务类目。 [图片] 5、其他医学健康服务:小程序内涉及提供非医疗级别检测服务:如运动检测、营养检测医疗级别基因检测,需补充医疗-其他医学健康服务类目。 所需资质(4选1): (1)《医疗机构执业许可证》 (2)《医疗器械生产许可证》 (3)合作医疗机构协议 (4)供应商医疗器械备案凭证及供货合同/凭证 案例:如图可看到,该小程序涉及非医疗级别基因检测服务推广等服务,需补充医疗-其他医学健康服务类目。 [图片] 6、药品非处方销售:小程序内涉及提供非处方药的在线交易、展示服务,需补充医疗-药品非处方销售类目。 所需资质(3选1): (1)《互联网药品信息服务资格证》与《药品生产许可证》(药品生产企业) (2)《互联网药品信息服务资格证》与《药品经营许可证》(药品批发企业) (3)《互联网药品信息服务资格证》与《药品经营许可证》(药品零售企业) 案例:如图可看到,该小程序涉及在线展示并销售非处方药品,需补充医疗-药品非处方销售类目。 [图片] 7、医疗器械信息展示:小程序内涉及医疗器械信息的展示,需补充医疗医疗器械信息展示类目。 (医疗器械范畴参考国家药监监督管理局官网:http://app1.sfda.gov.cn/datasearchcnda/face3/dir.html 所需资质:《互联网药品信息服务资格证》 案例:如图可看到,该小程序涉及在线展示医疗器械产品,需补充医疗医疗器械信息展示类目。 [图片] 8、药品信息展示:小程序内涉及药品信息的展示,需补充医疗-药品信息展示类目。 所需资质:互联网药品信息服务资格证》 案例:如图可看到,该小程序涉及药品信息展示,需补充医疗-药品信息展示类目。 [图片] 9、医疗器械生产企业:小程序内涉及医疗器械生产业务及展示,需补充医疗-医疗器械生产企业类目。 所需资质:《医疗器械生产许可证》 案例:如图可看到,该小程序涉及在线展示医疗器械生产厂家业务信息,需补充医疗-医疗器械生产企业类目。 [图片] 10、医疗器械自营:小程序内涉及医疗器械的在线经营和销售业务及展示。需补充医疗医疗器械自营类目。 所需资质(同时满足): (1)《互联网药品信息服务资格证书》; (2)《医疗器械生产许可证》或《医疗器械经营许可证》; 案例:如图可看到,该小程序涉及在线销售膏药类医疗器械产品,需补充医疗医疗医疗器械自营类目。 [图片] 11、医疗器械经营销售平台:小程序涉及为医疗器械用品类商家,提供平台入驻服务,补充医疗-医疗器械经营销售平台。 所需资质:《互联网药品信息服务资格证书》和 医疗器械网络交易服务第三方平台备案凭证(食药监局颁发) 案例:如图可看到,该小程序中多个不同医疗器械商家,在线销售医疗器械补充医疗-医疗器械经营销售平台。 [图片] 12、互联网医院:小程序提供网上看病、挂号、在线开药买药等全方位服务,补充医疗-互联网医院类目。 所需资质(2选1): (1)卫生健康部门的《设置医疗机构批准书》 (2)合作医院的《医疗机构执业许可证》与执业登记机关的审核合格文件 案例:如图可看到,该小程序涉及网上看病、挂号等全方位就医服务补充医疗-互联网医院类目。 [图片] 13、血液、干细胞服务:小程序涉及提供采集、存储血液和干细胞存储等服务。需补充医疗-血液、干细胞服务类目。 所需资质(3选1): (1)《血站执业许可证》及《承诺函》; (2)《单采血浆许可证》及《承诺函》; (3)政府有关部门批文及《承诺函》; 案例:如图可看到,该小程序涉及提供细胞储存服务,需补充医疗-血液、干细胞服务类目。 [图片] 14、临床试验:小程序内涉及提供临床试验信息及试验患者招募服务,需补充医疗-临床试验类目。 所需资质(4选1): 1、需提供2家或以上的具有临床试验资质的医疗机构的合作协议(2选1): (1.1)与具有临床试验资质的医疗机构之间的合作协议证明; (1.2)与医药公司之间的合作协议证明,以及医药公司与具有临床试验资质的医疗机构之间的合作协议 2、有临床试验资质医疗机构主体提供(2选1) : (1.1)公立医疗主体提供 :《事业单位法人证书》及《医疗机构执业许可证》及《药物临床试验机构资格认定证书》/《药物临床试验机构备案》 (1.2)私立医疗主体提供:《医疗机构执业许可证》及《药物临床试验机构资格认定证书》/《药物临床试验机构备案》 案例:如图可看到,该小程序涉及提供全国不同地区的药品受试者,需补充医疗-临床试验类目。 [图片] 15、非处方药销售平台:小程序涉及为入驻商家提供平台型的非处方药在线交易、展示等服务,需补充医疗-非处方药销售平台类目(该类目暂不支持在线销售处方药)。 所需资质:同时提供:《互联网药品信息服务资格证》与《增值电信业务经营许可证》(核准服务项目包含“在线数据处理与交易处理业务“或“互联网信息服务业务”) 案例:如图可看到,该小程序涉及为多家入驻药商提供非处方药销售的平台型服务,需补充医疗-非处方药销售平台类目。 [图片]
2021-08-04 - 平台型服务商代实现企业官网小程序(云开发版)
一、帐号准备1 第三方平台类型选择:平台型服务商 2 权限选择:1)开发管理与数据分析权限 2)云开发管理权限集 3 开发配置1)登录授权的发起页域名:w.cloudbase.vip 2)授权事件接收URL:wx.cloudbase.vip/call 注意:在这里只是教程演示的URL,请根据自己的域名设置 请根据以上内容在微信开放平台注册一个第三方平台帐号,具体流程可以参照第一章节的步骤。 二、基础信息设置1 开发环境搭建1.1 开通云开发CloudBase,并设定域名云开发CloudBase是由腾讯云推出的云原生一体化多端研发平台,用户无需管理底层架构,可以直接开展业务开发。我们的第三方平台(注意不是小程序本身)使用云开发来构建。如果你想参照服务器版本,请在教程最后下载服务器版本的DEMO,代码细节一致。 开通云开发CloudBase,创建按量计费环境,进入静态网站托管页面的【基础配置】,添加自定义域名。需要注意,此处的自定义域名需要与第三方平台设置的授权发起页域名保持一致。 在本教程中,设定域名为w.cloudbase.vip [图片] 1.2 设定授权事件url域名在控制台中点击【环境-云接入】,进入云接入配置页,配置第三方平台设置的授权事件接收URL所在的域名,在本教程中,设定的授权事件url域名为wx.cloudbase.vip 云接入是使用标准的 HTTP 协议访问环境内全部资源的一种能力,包括云函数服务,通过对应的触发域名以及云函数的触发路径进行http访问,便可以使用云函数的服务。具体可以参考:云接入文档[图片] 2 创建授权事件接收URL的服务监听安装云开发vscode插件,在vscode中,创建一个云函数,命名为call。 [图片] [图片] 创建后,我们开始进行代码的编写,首先在index.js文件中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const cloud = tcb.init({ env: "" //需要填写自己的云开发环境id }); const db = cloud.database(); const _ = db.command; exports.main = async (event) => { //从event中可以获取到HTTP请求的有关信息 //event.body即为请求体 let msg_body = event.body; /*event.queryStringParameters中可以获得请求参数,取出以下三个内容 - timestamp 时间戳 - nonce 随机数 - msg_signature 消息体签名,用于验证消息体的正确 */ let { msg_signature, nonce, timestamp } = event.queryStringParameters; //判断签名是否不为空,过滤一些非开放平台的无效请求 if (msg_signature != null) { //针对信息进行base64解码 let encryptedMsg = Buffer.from(msg_body, 'base64').toString(); //取出加密的encrypt,在这里没有使用XML方式读取 let encrypt = encryptedMsg.slice(encryptedMsg.indexOf('')); //引入util.js文件,命名为WechatEncrypt,此js包含解码的所有逻辑 const WechatEncrypt = require('./util'); //引入key.json,在这里存储了第三方平台设置的key,随机码,appid等 const WXKEY = require('./key.json'); //将key.json的内容代入,创建WechatEncrypt实例 const wechatEncrypt = new WechatEncrypt(WXKEY); //将timestamp 时间戳、nonce 随机数、加密的encrypt代入gensign函数进行签名处理 let signature = wechatEncrypt.genSign({ timestamp, nonce, encrypt }); //判断签名是否和传来的参数签名一致 if (signature === msg_signature) { //将加密的encrypt直接代入decode函数进行解码,返回解码后的明文 let xml = wechatEncrypt.decode(encrypt); //判断明文中是否有ComponentVerifyTicket字段,由此来判断此为验证票据 if (xml.indexOf('ComponentVerifyTicket') != -1) { //取出相应的票据,在这里没有使用XML方式读取 let ticket = xml.slice(xml.indexOf('ticket@@@'), xml.indexOf(']]>')); try { //将票据信息保存到云开发数据库中wxid集合中,component_verify_ticket文档中,在使用前需要在控制台创建集合并设置文档 db.collection('wxid').doc('component_verify_ticket').update({ time: db.serverDate(),//更新的时间 value: ticket }); } catch (e) { console.log('save failed!', e); } } return 'success'; } else { return 'error'; } } else { return 404; } } 在index.js同级目录下,创建util.js,此为解码的主要逻辑文件,编写代码如下: const crypto = require('crypto') const ALGORITHM = 'aes-256-cbc' // 使用的加密算法 const MSG_LENGTH_SIZE = 4 // 存放消息体尺寸的空间大小。单位:字节 const RANDOM_BYTES_SIZE = 16 // 随机数据的大小。单位:字节 const BLOCK_SIZE = 32 // 分块尺寸。单位:字节 let data = { appId: '', // 微信公众号 APPID token: '', // 消息校验 token key: '', // 加密密钥 iv: '' // 初始化向量 } const Encrypt = function (params) { let { appId, encodingAESKey, token } = params let key = Buffer.from(encodingAESKey + '=', 'base64') // 解码密钥 let iv = key.slice(0, 16) // 初始化向量为密钥的前16字节 Object.assign(data, { appId, token, key, iv }) } Encrypt.prototype = { /** * 加密消息 * @param {string} msg 待加密的消息体 */ encode(msg) { let { appId, key, iv } = data let randomBytes = crypto.randomBytes(RANDOM_BYTES_SIZE) // 生成指定大小的随机数据 let msgLenBuf = Buffer.alloc(MSG_LENGTH_SIZE) // 申请指定大小的空间,存放消息体的大小 let offset = 0 // 写入的偏移值 msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset) // 按大端序(网络字节序)写入消息体的大小 let msgBuf = Buffer.from(msg) // 将消息体转成 buffer let appIdBuf = Buffer.from(appId) // 将 APPID 转成 buffer let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf]) // 将16字节的随机数据、4字节的消息体大小、若干字节的消息体、若干字节的APPID拼接起来 let cipher = crypto.createCipheriv(ALGORITHM, key, iv) // 创建加密器实例 cipher.setAutoPadding(false) // 禁用默认的数据填充方式 totalBuf = this.PKCS7Encode(totalBuf) // 使用自定义的数据填充方式 let encryptdBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()]) // 加密后的数据 return encryptdBuf.toString('base64') // 返回加密数据的 base64 编码结果 }, /** * 解密消息 * @param {string} encryptdMsg 待解密的消息体 */ decode(encryptdMsg) { let { key, iv } = data let encryptedMsgBuf = Buffer.from(encryptdMsg, 'base64') // 将 base64 编码的数据转成 buffer let decipher = crypto.createDecipheriv(ALGORITHM, key, iv) // 创建解密器实例 decipher.setAutoPadding(false) // 禁用默认的数据填充方式 let decryptdBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()]) // 解密后的数据 decryptdBuf = this.PKCS7Decode(decryptdBuf) // 去除填充的数据 let msgSize = decryptdBuf.readUInt32BE(RANDOM_BYTES_SIZE) // 根据指定偏移值,从 buffer 中读取消息体的大小,单位:字节 let msgBufStartPos = RANDOM_BYTES_SIZE + MSG_LENGTH_SIZE // 消息体的起始位置 let msgBufEndPos = msgBufStartPos + msgSize // 消息体的结束位置 let msgBuf = decryptdBuf.slice(msgBufStartPos, msgBufEndPos) // 从 buffer 中提取消息体 return msgBuf.toString() // 将消息体转成字符串,并返回数据 }, /** * 生成签名 * @param {Object} params 待签名的参数 */ genSign(params) { let { token } = data let { timestamp, nonce, encrypt } = params; let rawStr = [token,timestamp,nonce,encrypt].sort().join('') // 原始字符串 let signature = crypto.createHash('sha1').update(rawStr).digest('hex') // 计算签名 return signature }, /** * 按 PKCS#7 的方式从填充过的数据中提取原数据 * @param {Buffer} buf 待处理的数据 */ PKCS7Decode(buf) { let padSize = buf[buf.length - 1] // 最后1字节记录着填充的数据大小 return buf.slice(0, buf.length - padSize) // 提取原数据 }, /** * 按 PKCS#7 的方式填充数据结尾 * @param {Buffer} buf 待填充的数据 */ PKCS7Encode(buf) { let padSize = BLOCK_SIZE - (buf.length % BLOCK_SIZE) // 计算填充的大小。 let fillByte = padSize // 填充的字节数据为填充的大小 let padBuf = Buffer.alloc(padSize, fillByte) // 分配指定大小的空间,并填充数据 return Buffer.concat([buf, padBuf]) // 拼接原数据和填充的数据 } } module.exports = Encrypt 在index.js同级目录下创建key.json,里面保存有关设置信息 { "appId": "第三方平台详情页中展示的appid", "encodingAESKey": "在创建过程中设置的消息加解密Key", "token": "在创建过程中设置的消息校验Token" } 按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk crypto 在call文件夹中右键,点击部署并上传全部文件,云函数即可部署成功。 [图片] 前往云开发CloudBase控制台,在云函数操作页下找到部署的call云函数,点击进入函数详情页,点击右上角编辑,进入设置页,配置云接入路径为/call [图片] 于是,我们可以通过wx.cloudbase.vip/call这个地址来接收请求。微信开放平台将每隔10分钟左右就向此url发送请求(因为我们在第三方平台创建时填写的此url),此云函数便可以完成请求的解析和解密存储操作。具体效果如下: [图片] [图片] 3 使用接收到的验证票据(component_verify_ticket)获取令牌在vscode中,我们创建云函数getComToken,按照之前创建的步骤正常创建即可。在index.js编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid, component_appsecret } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(ticket) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_component_token',//请求的API地址 body: JSON.stringify({ component_appid, component_appsecret, component_verify_ticket: ticket }),//传递的所需参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { console.log(event); try { //由于令牌有一定时效性,所以我们没必要每一次都要请求,而是将令牌保存重复利用,我们将令牌保存在wxid集合中的component_access_token文档里 //首先取出文档的信息 let access_token = (await db.collection('wxid').doc('component_access_token').get()).data[0]; //以当前时间的往后一分钟来作为上限时间 let overtime = new Date((new Date()).valueOf() + 60 * 1000); //如果文档的令牌超时时间大于上限时间,则证明令牌还有效,直接返回令牌 if (access_token.time > overtime) { return access_token.value; } else { //如果小于则证明令牌过期,需要重新申请 console.log('token timeover!'); try { //取出ticket票据信息 let ticket = (await db.collection('wxid').doc('component_verify_ticket').get()).data[0]; //将票据信息传入http请求函数,等待请求结果 let result = await CallWeb(ticket.value); //结果是一个json字符串,验证是否有component_access_token字样,如果有则没有报错 if (result.indexOf('component_access_token') != -1) { //解析字符串为json let { component_access_token, expires_in } = JSON.parse(result); try { //更新令牌,并设定超时时间为当前时间的有效时效后,expires_in为有效秒数 await db.collection('wxid').doc('component_access_token').update({ time: db.serverDate({ offset: expires_in * 1000 }), value: component_access_token }); //返回新的令牌 return component_access_token; } catch (e) { console.log('access save failed!', e); return null; } } else { console.log('wxcall failed!', result); return null; } } catch (e) { console.log('ticket failed!', e); return null; } } } catch (e) { console.log('access get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid", "component_appsecret": "第三方平台 appsecret" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,进入函数的编辑设置页,按照截图进行设置并保存。 [图片] 保存后会出现固定的ip地址,如下图所示: [图片] 将此处的ip地址填写到第三方平台的白名单中,修改保存 [图片] 获取令牌的接口云函数,具体效果如下: [图片] [图片] 注意事项云开发的云函数可以设置固定IP地址,这样就可以直接将IP地址填写到第三方平台白名单中。在同一环境中所有云函数共用一个IP地址,无需重复设置。 温馨提示:以js来演示消息解密过程,其中util文件中仍然包含加密函数,有需要的同学可以自行研究使用,util无需修改直接可用,如果你想使用其他语言版本,请在官方文档中下载代码示例 三、授权流程配置1 使用令牌获取预授权码并拼接用户授权链接根据官方文档-获取预授权码的描述,我们需要component_access_token(第三方平台令牌)、component_appid(第三方平台appid),API接口为:https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=${component_access_token} 使用vscode创建一个云函数,命名为getPreAuth,按照之前正常的创建过程创建即可。在index.js中编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(token) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=' + token, body: JSON.stringify({ component_appid }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //此云函数是由后台管理页面请求的,在调用此云函数时就意味着想要创建一个授权的对象,name是对这个对象的备注 if(event.name==null||event.name=='') return null; try { //通过调用getComToken云函数获取第三方令牌 let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { //将令牌信息传入http请求函数,等待请求结果 let result = await CallWeb(access_token); //结果是一个json字符串,验证是否有pre_auth_code字样,如果有则没有报错 if (result.indexOf('pre_auth_code') != -1) { //解析字符串为json let { pre_auth_code, expires_in } = JSON.parse(result); //拼接授权链接,根据此文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Authorization_Process_Technical_Description.html //其中redirect_uri填写发起授权的url,这在之后配置 let auth_url = `https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=${component_appid}&pre_auth_code=${pre_auth_code}&redirect_uri=https://w.cloudbase.vip&auth_type=2`; //将授权链接保存到云开发数据库中的mini集合内 return await db.collection('mini').add({ status: 0, name:event.name, url: auth_url, time: db.serverDate({ offset: expires_in * 1000 }) }) } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 完成后,可以在控制台按照如下方法进行测试,具体效果如下: [图片] 我们可以保存返回结果中的id值,这将在下一步骤中用到。 2 通过授权发起页域名打开授权链接引导用户扫码由于授权链接需要从设置的发起页域名中跳转才有效,所以我们需要编写一个html文档,部署到静态网站托管服务中,由此来模拟真实业务场景下用户自助授权的整个流程。 我们创建一个html文档,编写如下代码: 客户授权 这是微信第三方平台测试——客户端接口 let msg_text = document.getElementById('text'); let app = tcb.init({ env: ''//此处填写自己的云开发环境id }); let auth = app.auth(); //加载后直接使用云开发匿名登录方式进行登录,此处需要在控制台中开启匿名登录 auth.anonymousAuthProvider().signIn().then(() => { console.log('匿名登录成功!'); //拉取url中的参数 let query = getQueryString(); //如果存在do这个参数,则执行逻辑 if (query.do != null) { msg_text.innerText = "拉取授权页面中……" //使用参数信息,其实就是上一步保存数据库返回的文档id,请求云函数authGetOne app.callFunction({ name: 'authGetOne', data: { id: query.do } }).then(res => { console.log(res); //存在数据 if (res.result.data.length != 0) { //状态设置0为未授权,1为已授权 if(res.result.data[0].status==0){ //保存id信息,因为授权后要返回此界面,需要后续解析 window.localStorage.setItem('open_auth_id', query.do); //跳转url window.location = res.result.data[0].url; } else{ msg_text.innerText = "已授权!" } } else { msg_text.innerText = "授权路径不存在!" } }); } }); // 获取url的请求参数 function getQueryString() { var qs = location.search.substr(1), args = {}, items = qs.length ? qs.split("&") : [], item = null, len = items.length; for (var i = 0; i < len; i++) { item = items[i].split("="); var name = decodeURIComponent(item[0]), value = decodeURIComponent(item[1]); if (name) { args[name] = value; } } return args; } 将此html命名为index.html,上传至环境的静态网站托管中。并需要在环境配置登录鉴权中开启匿名登录 [图片] 在vscode中创建云函数,命名为authGetOne,按照之前正常的创建即可。在index.js中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const cloud = tcb.init({ env: ""//填写自己云开发环境的ID }); const db = cloud.database(); exports.main = async event => { if(event.id!=null){ //field控制取出哪些变量,敏感信息不外泄 return await db.collection('mini').where({_id:event.id}).field({ url:true, status:true }).get(); } else{ return null; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk 右键上传部署云函数。 我们就可以使用w.cloudbase.vip?do=+(上一步保存的id)这个链接来实现授权页面的跳转了。(w.cloudbase.vip为本教程专用,实际实践需要改写为自己的) 3 获取第三方调用令牌3.1 根据用户授权后的授权码获取用户授权账号的信息用户授权后,会在回调的页面中回传授权码,具体表现如下: [图片] 其中auth_code参数即为授权码的信息,我们改造一下index.html,在if逻辑中添加else if逻辑 //加载后直接使用云开发匿名登录方式进行登录,此处需要在控制台中开启匿名登录 auth.anonymousAuthProvider().signIn().then(() => { console.log('匿名登录成功!'); //拉取url中的参数 let query = getQueryString(); //如果存在do这个参数,则执行逻辑 if (query.do != null) { msg_text.innerText = "拉取授权页面中……" //使用参数信息,其实就是上一步保存数据库返回的文档id,请求云函数authGetOne app.callFunction({ name: 'authGetOne', data: { id: query.do } }).then(res => { console.log(res); //存在数据 if (res.result.data.length != 0) { //状态设置0为未授权,1为已授权 if(res.result.data[0].status==0){ //保存id信息,因为授权后要返回此界面,需要后续解析 window.localStorage.setItem('open_auth_id', query.do); //跳转url window.location = res.result.data[0].url; } else{ msg_text.innerText = "已授权!" } } else { msg_text.innerText = "授权路径不存在!" } }); } //如果存在auth_code这个变量 else if (query.auth_code != null) { msg_text.innerText = "校验中……" //取出保存的id信息 let temp_id = window.localStorage.getItem('open_auth_id'); if (temp_id != null) { //执行authUpdateOne进行授权信息获取 app.callFunction({ name: 'authUpdateOne', data: { code: query.auth_code, id: temp_id } }).then(res => { console.log(res); if (res.result!= null) { //授权信息获取成功后,删除无用的id信息 window.localStorage.removeItem('open_auth_id'); console.log(res.result); msg_text.innerText = "校验成功!"; } else { msg_text.innerText = "校验失败,请刷新此页面重试!" } }); } else { msg_text.innerText = "路径已经刷新,请联系平台!" } } }); 将此html重新上传至静态网站托管 在vscode中创建云函数,命名为authUpdateOne,按照之前正常的创建即可。在index.js中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(token,code) { return new Promise((resolve, reject) => { request({ //获取授权信息的API url: 'https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=' + token, body: JSON.stringify({ component_appid, authorization_code:code }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { if(event.code==null||event.id==null) return null; try { //通过调用getComToken云函数获取第三方令牌 let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { //将令牌信息和授权码传入http请求函数,等待请求结果 let result = await CallWeb(access_token,event.code); console.log(result); //结果是一个json字符串,验证是否有authorization_info字样,如果有则没有报错 if (result.indexOf('authorization_info') != -1) { //解析字符串为json let { authorization_info } = JSON.parse(result); //取出字段 let { authorizer_access_token,authorizer_appid,authorizer_refresh_token,expires_in,func_info} = authorization_info; //存储到mini中相应的文档里,并置状态为1 return await db.collection('mini').doc(event.id).update({ status:1, time:db.serverDate(), func_info, access_token:authorizer_access_token, access_time:db.serverDate({offset: expires_in * 1000}), appid:authorizer_appid, refresh_token:authorizer_refresh_token }); } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 具体效果如下图所示: [图片] 以上是在用户的角度来看到的,在第三方平台角度看到的信息如下,我们可以获得授权账户的appid、接口令牌、刷新令牌、授权集信息。 由于授权用户可以自主修改授权的信息,所以我们需要判断用户授权的授权集是否满足我们的业务开发要求,在下图字段func_info我们可以获得权限集合的列表,我们应该有相应的判断,来进行复核确认,官方文档中已经清楚的写明了。 一切确定了之后,我们便可以根据这些来进行后续的开发操作了。 [图片] 3.2 过期的授权令牌如何重新刷新在官方文档中描述,每次提供令牌时,都附带刷新令牌,我们需要通过刷新令牌来去获取最新的令牌,由此反复。 需要注意的是,要好好保存刷新令牌,如果丢失需要用户重新授权才可以。 以下是关于重复获取令牌的云函数示例,具体部署细则参照前面的云函数 const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: "" }); const db = cloud.database(); const _ = db.command; function CallWeb(refresh_token, access_token, auth_appid) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=' + access_token, body: JSON.stringify({ component_appid: component_appid, authorizer_appid: auth_appid, authorizer_refresh_token: refresh_token }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { console.log(event); if(event.appid==null)return; try { let auth_data = (await db.collection('mini').where({ appid: event.appid }).get()).data; if (auth_data.length != 0) { let { access_token, access_time, refresh_token } = auth_data[0]; let overtime = new Date((new Date()).valueOf() + 60 * 1000); if (access_time > overtime) { return access_token; } else { console.log('token timeover!'); let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { let result = await CallWeb(refresh_token, access_token, event.appid); console.log(result); if (result.indexOf('authorizer_access_token') != -1) { let { authorizer_access_token, authorizer_refresh_token, expires_in } = JSON.parse(result); await db.collection('mini').where({appid:event.appid}).update({ access_token: authorizer_access_token, access_time: db.serverDate({ offset: expires_in * 1000 }), refresh_token: authorizer_refresh_token }); return authorizer_access_token } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } } else { console.log('can not get appid!'); return null; } } catch (e) { console.log('access get failed!', e); return null; } } 我们可以通过此云函数直接获取令牌,如果令牌过期会自动刷新令牌并保存,以备下一次使用。 四、 使用小程序云开发构建小程序模版我们开始开发编写一个小程序,为了能够较为完整的演示所有可能性,此小程序是一个使用后端服务的企业展示小程序,具体效果如下: [图片] 此小程序用到云开发,所有内容均来自云开发数据库,并且通过云函数进行更新操作。 具体代码可以点击这里下载,其中关键的逻辑信息如下: 小程序页面index.js代码如下: const app = getApp() var that = null; Page({ onShow() { //加载本地存储 let data = wx.getStorageSync('data'); //如果没有存储,直接请求云端数据 if(data==null||data==""){ this.init(); } else{ //有数据即直接部署数据 this.setData(data); wx.setNavigationBarTitle({ title: data.name, }) } }, //云端请求数据,云开发云函数 init(){ that = this; wx.cloud.callFunction({ name: "test",//云函数名称为test success(res) { //部署数据,设置标题栏内容 if(res.result.name!=null){ that.setData(res.result); wx.setStorageSync('data', res.result); wx.setNavigationBarTitle({ title: res.result.name, }) } }, fail(e){ wx.showModal({ title:"测试失败", content:JSON.stringify(e) }); } }) }, telcall(){ wx.makePhoneCall({ phoneNumber: this.data.tel, }) } }) 云函数test逻辑代码如下: const cloud = require('wx-server-sdk'); cloud.init(); const db = cloud.database(); const _ = db.command; exports.main = async (event, context) => { try{ // 读取数据库中的INFO集合的base_info文档 let FAS = await db.collection('INFO').doc('base_info').get(); // 附加的一个计数,每次调用就加一 await db.collection('INFO').doc('base_info').update({ data:{ see:_.inc(1) } }); // 返回数据 return FAS.data; } catch(e){ return { _id:null }; } } 在开发完毕后,即可上传到第三方平台的草稿箱,参照第一章节的步骤将其转化为小程序模版,即可使用模版ID进行下面的部署步骤了。 五、小程序模版部署到小程序中(上传代码与提审)1 小程序模版部署到授权小程序在这里我们将使用第三方平台的API接口来执行部署过程。由于所有第三方API接口都需要在白名单中才可以调用,所以我们创建一个云函数来实现。 打开vscode,创建一个云函数,命名为uploadCode,按照之前的指引正常操作即可,打开index.js编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: "" //此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(access_token, data) { return new Promise((resolve, reject) => { request({ //上传小程序代码的API url: `https://api.weixin.qq.com/wxa/commit?access_token=${access_token}`, body: JSON.stringify(data), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //取出传参的appid和企业名称(业务) let { appid,name } = event; if (appid != null && name != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入令牌,以及参数 let result = await CallWeb(access_token, { template_id : "2",//模板id,要换成自己的真实的 user_version : "1.0.1",//版本号,自定义 user_desc : "第三方平台代开发",//描述 ext_json : `{ "extEnable": true, "extAppid": "${appid}", "directCommit": false, "window":{ "navigationBarTitleText": "${name}" }}`//这里是特殊配置,会覆盖小程序代码原有配置,比如在这里将标题栏名字设置为企业名字了 }); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 我们使用接口调用如下,具体效果如下图所示: [图片] 当然我们真实管理不可能以这种方式来进行,所以需要写一个管理平台,利用这个封装好的函数接口来实现业务操作。这就是学习完成之后需要自己搭建的内容了。 本教程提供了一个简易的管理平台,可以予以参考。 2 授权小程序云开发资源的部署云开发作为后端服务的小程序,第三方平台提供了丰富的API接口来部署各种资源。官方文档-云开发接入指南主要描述了云开发环境层面的一些部署能力,官方文档-云开发HTTP API主要描述了云开发业务层面的一些能力,包括数据库存储等操作。 所有的API接口都是以https://api.weixin.qq.com/tcb/开头,整体的参数都是比较固定,所以我们可以将接口通用化成一个云函数。 打开vscode,我们创建一个云函数,命名为toTCB,编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的云开发通用http请求函数 function CallWeb(access_token, api_name, data) { return new Promise((resolve, reject) => { request({ url: `https://api.weixin.qq.com/tcb/${api_name}?access_token=${access_token}`,//拼接相应的name,以及令牌 body: JSON.stringify(data),//请求的数据参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //获取appid,相应的云开发接口名、参数 let { appid, name, data } = event; if (appid != null && name != null && data != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入参数,等待数据 let result = await CallWeb(access_token, name, data); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 我们可以在管理平台中直接请求此云函数,来进行云开发相关部署,这是客户端封装的云函数调用,需要在登录后才可以使用 function toTCB(obj) { app.callFunction({ name: 'toTcb', data: { appid: obj.appid, name: obj.name, data: obj.data } }).then(res => { obj.success ? obj.success(res.result) : null; }).catch(e => { obj.fail ? obj.fail(e) : null; }); } 以下举几个例子供参考: 1) 查询小程序的云开发环境 toTCB({ name: 'getenvinfo', appid: appid, data: {}, success(res) { console.log(res); if (res.errcode == 0 && res.info_list != 0) { console.log('检测到' + res.info_list.length + '个云开发环境,使用默认环境id:' + res.info_list[0].env); //在这里如果没有云开发环境可以使用创建环境的接口来创建,在参考示例中只有创建云开发环境的情况,需要自己改写实现 } else { alert('环境检测失败!'); } } }) 2)文件上传至云存储 toTCB({ name: 'uploadfile', appid: input.appid, data: { env: env, path: 'logo.png' }, success(res) { console.log(res); if (res.errcode == 0) { //在这里返回了一个上传链接,需要再使用http请求上传文件。 //需要注意的是,如果在前端上传文件,需要以微信公众号形式登录腾讯云控制台-添加web安全域名为操作的前端域名才可以 } else { alert('文件上传失败!'); } } }) 3)数据库的集合创建 toTCB({ name: 'databasecollectionadd', appid: appid, data: { env: env, collection_name: 'INFO' }, success(res) { console.log(res); if (res.errcode == 0 || res.errcode == -501000) { alert('创建数据库INFO成功!'); //此处注意-501000是表示数据库已存在,目的达成 } else { alert('创建数据库INFO失败!'); } } }) 4)数据库处理数据 toTCB({ name: 'databaseadd', appid: appid, data: { env: env, query: ` db.collection('INFO').add({ data:{ _id:"base_info", content:'${des}', logo:"${logourl}", name:"${name}", person:"${person}", see:0, tel:"${tel}" } }) `//拟定数据库处理语句 }, success(res) { console.log(res); if (res.errcode == 0) { alert('数据库数据部署成功!'); } else if (res.errcode == -501000) { alert('数据库有原始数据,覆盖新数据!'); toTCB({ name: 'databaseupdate', appid: appid, data: { env: env, query: ` db.collection('INFO').doc('base_info').update({ data:{ content:'${des}', logo:"${logourl}", name:"${name}", person:"${person}", see:0, tel:"${tel}" } }) ` }, success(res) { console.log(res); if (res.errcode == 0) { alert('数据库数据部署成功!'); } } }) } } }) 3 授权小程序提交审核当小程序模版上传、云开发资源部署完毕之后,就需要提交审核了。 相关代码如下: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(access_token,data) { return new Promise((resolve, reject) => { request({ url: `https://api.weixin.qq.com/wxa/submit_audit?access_token=${access_token}`,//拼接相应的name,以及令牌 body: JSON.stringify(data),//请求的数据参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //获取appid,参数 let { appid } = event; if (appid != null && name != null && data != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入参数,等待数据 let result = await CallWeb(access_token,{ version_desc:"这是我的小程序,请审核" }); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 代码示例本教程用到的示例代码如下: 全代码示例-云开发版本
2021-09-09 - 注册小程序使用的营业执照公司名称变更了,怎么同步小程序主体?
准备相关资料,提单同步主体即可。 1、小程序帐号 2、帐号的营业执照或者组织机构代码证: 3、法人/运营者/提交人的手持身份证照 4、变更后的主体名称: 5、变更后的相关营业执照或者组织机构代码证: 6、工商局变更证明: 手机微信端提单链接:https://kf.qq.com/touch/bill/171208selfqaa5952b5e.html
2019-08-30 - 小程序模板消息能力调整通知
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019-10-13 - 小程序内用户帐号登录规范调整和优化建议
为更好地保护用户隐私信息,优化用户体验,平台将会对小程序内的帐号登录功能进行规范。本公告所称“帐号登录功能”是指开发者在小程序内提供帐号登录功能,包括但不限于进行的手机号登录,getuserinfo形式登录、邮箱登录等形式。具体规范要求如下: 1.服务范围开放的小程序 对于用户注册流程是对外开放、无需验证特定范围用户,且注册后即可提供线上服务的小程序,不得在用户清楚知悉、了解小程序的功能之前,要求用户进行帐号登录。 包括但不限于打开小程序后立即跳转提示登录或打开小程序后立即强制弹窗要求登录,都属于违反上述要求的情况; 以下反面示例,在用户打开小程序后立刻弹出授权登录页; [图片] 建议修改为如下正面示例形式:在体验小程序功能后,用户主动点击登录按钮后触发登录流程,且为用户提供暂不登录选项。 [图片] 2.服务范围特定的小程序 对于客观上服务范围特定、未完全开放用户注册,需通过更多方式完成身份验证后才能提供服务的小程序,可以直接引导用户进行帐号登录。例如为学校系统、员工系统、社保卡信息系统等提供服务的小程序; 下图案例为正面示例:校友管理系统,符合规范要求。 [图片] 3.仅提供注册功能小程序 对于线上仅提供注册功能,其他服务均需以其他方式提供的小程序,可在说明要求使用帐号登录功能的原因后,引导用户进行帐号注册或帐号登录。如ETC注册申请、信用卡申请; 如下反面示例,用户在进入时未获取任何信息,首页直接强制弹框要求登录注册ETC,这是不符合规范的。 [图片] 建议修改为如下正面示例所示形式:允许在首页说明注册功能后,提供登录或注册按钮供用户主动选择点击登录。 [图片] 4.提供可取消或拒绝登录选项 任何小程序调用帐号登录功能,应当为用户清晰提供可取消或拒绝的选项按钮,不得以任何方式强制用户进行帐号登录。 如下图所示反面示例,到需要登录环节直接跳转登录页面,用户只能选择点击登录或退出小程序,这不符合登录规范要求。 [图片] 建议修改为下图正面示例形式,在需帐号登录的环节,为用户主动点击登录,并提供可取消按钮,不强制登录。 [图片] 针对以上登录规范要求,平台希望开发者们能相应地调整小程序的帐号登录功能。如未满足登录规范要求,从2019年9月1日开始,平台将会在后续的代码审核环节进行规则提示和修改要求反馈。
2019-07-20 - 微信人脸核身接口能力
一、能力背景 近年来,国家在医疗挂号、APP注册、快递收寄、客运、运营商等多领域规定,需要用户实名才可办理业务,预计后续也会有越来越多的此类法规。因此,微信参照公安部“互联网+”可信身份认证服务平台标准,依托腾讯公司及微信的生物识别技术,建立微信“实名实人信息校验能力” ,即通过人脸识别+权威源比对,校验用户实名信息和本人操作(简称微信人脸核身)。 目前接口限定主体及行业类目开放公测,提供给资质符合要求的业务方,在合适的业务场景内使用。目前仅支持持二代身份证的大陆居民。 由于人脸核身功能涉及到用户的敏感、隐私信息,因此调用此接口的小程序,需要满足一定的条件。即:小程序的主体以及类目,需要在限定的类目范围内,且与小程序的业务场景一致。开展的业务也需要是国家相关法规、政策规定的需要“实名办理”的相关业务(其他未在范围内的业务,则暂不支持)。 以下为接口接入及开发的详细内容。如开发中遇到任何疑问,可以点击此处通过社区反馈,将有工作人员跟进回复。 文档第四部分【再次获取核验结果api】,有助于提高业务方安全性,请务必接入! 现阶段微信人脸核验能力,针对小程序,开放的主体类目范围包含: 小程序一级类目 小程序二级类目 小程序三级类目 使用人脸核验接口所需资质 物流服务 收件/派件 / 《快递业务经营许可证》 物流服务 货物运输 / 《道路运输经营许可证》(经营范围需含网络货运) 教育 学历教育(学校) / (2选1):1、公立学校:由教育行政部门出具的审批设立证明 或 《事业单位法人证书》;2、私立学校:《民办学校办学许可证》与《民办非企业单位登记证书》 医疗 公立医疗机构 / 《医疗机构执业许可证》与《事业单位法人证书》 医疗 互联网医院 / 仅支持公立医疗机构互联网医院(2选1):1、卫生健康部门的《设置医疗机构批准书》;2、 《医疗机构执业许可证》(范围均需含“互联网诊疗”或名称含“互联网医院”等相关内容 医疗服务 三级私立医疗机构 / 仅支持三级以上私立医疗机构,提供《医疗机构执业许可证》、《营业执照》及《医院等级证书》 政务民生 所有二级类目 / 仅支持政府/事业单位,提供《组织机构代码证》或《统一社会信用代码证》。 金融业 银行 / (2选1):1、《金融许可证》; 2、《金融机构许可证》。 金融业 信托 / (2选1):1、《金融许可证》; 2、《金融机构许可证》。 金融业 公募基金 / (4选1):1、《经营证券期货业务许可证》且业务范围必须包含“基金”;2、《基金托管业务许可证》; 3、《基金销售业务资格证书》;4、《基金管理资格证书》。 金融业 证券/期货 / 《经营证券期货业务许可证》 金融业 保险 / (8选1):1、《保险公司法人许可证》;2、《经营保险业务许可证》;3、《保险营销服务许可证》;4、《保险中介许可证》;5、《经营保险经纪业务许可证》;6、《经营保险公估业务许可证》或《经营保险公估业务备案》;7、《经营保险资产管理业务许可证》 ;8、《保险兼业代理业务许可证》。 金融业 消费金融 / 银监会核准开业的审批文件与《金融许可证》与《营业执照》。 金融业 非金融机构自营小额贷款 / 仅支持省金融办监管的网络小贷主体,同时提供:1、《小额贷款公司经营许可证》或《小额贷款机构经营许可证》或省金融办批准文件;2、申请主体资质承诺函。 金融业 汽车金融/金融租赁 / 仅支持汽车金融/金融租赁主体,同时提供:1、《营业执照》(公司名称包含“汽车金融” /“金融租赁”;营业范围包含“汽车金融”/“金融租赁”业务);2、《金融许可证》或银保监会及其派出机构颁发的开业核准批复文件。 交通服务 网约车 快车/专车/其他网约车 (自营性网约车)提供《网络预约出租汽车经营许可证》。(网约车平台)提供与网约车公司的合作协议以及合作网约车公司的《网络预约出租汽车经营许可证》。 交通服务 航司服务 / 提供《公共航空运输企业经营许可证》 交通服务 机场服务 / 提供《民用机场使用许可证》或《运输机场使用许可证》 交通服务 公交/地铁 / 提供公交/地铁/交通卡公司《营业执照》 交通服务 水运 / (船企)提供《水路运输许可证》。(港口)提供《港口经营许可证》 交通服务 骑车 / 仅支持共享单车,提供共享单车公司《营业执照》 交通服务 火车/高铁/动车 / 仅支持铁路局/公司官方,提供铁路局/公司《营业执照》 交通服务 长途汽车 / (2选1):1、《道路运输经营许可证》(经营范围需含客运);2、官方指定联网售票平台(授权或协议或公开可查询文件)。 交通服务 租车 / 运营公司提供《备案证明》与对应公司《营业执照》,且营业执照中包含汽车租赁业务 交通服务 高速服务 / 仅支持ETC发行业务,(2选1):1、事业单位主体,需提供《事业单位法人证书》;2、官方指定的发行单位(一发单位),需提供“官方授权或协议,或公开可查询的文件”; 生活服务 生活缴费 / (供电类)提供《电力业务许可证》与《营业执照》,且《营业执照》且经营范围含供电。(燃气类)提供《燃气经营许可证》与《营业执照》,且《营业执照》且经营范围含供气。(供水类)提供《卫生许可证》与《营业执照》。(供热类)提供《供热经营许可证》与《营业执照》,且《营业执照》且经营范围含供热。 IT科技 基础电信运营商 / (2选1):1、基础电信运营商:提供《基础电信业务经营许可证》;2、运营商分/子公司:提供营业执照(含相关业务范围)。 IT科技 转售移动通信 / 仅支持虚拟运营商,提供《增值电信业务许可证》(业务种类需含通过转售方式提供移动通信业务) 旅游服务 住宿服务 / 仅支持酒店,提供《酒店业特种行业经营许可证》 旅游服务 旅游管理单位 / 仅支持政府/事业单位,提供《组织机构代码证》或《统一社会信用代码证》 商业服务 公证 / 仅支持公证处,提供《公证处执业许可证》或《事业单位法人证书》 社交 直播 / (2选1):1、《信息网络传播视听节目许可证》;2、《网络文化经营许可证》(经营范围含网络表演)。 如对以上类目或资质有疑问,可点击参考小程序“非个人主体开放的服务类目”,详细了解小程序开放的服务类目及对应资质。 二、准备接入 (请在小程序发布后,再提交人脸核身接口申请) 满足第一节中描述的类目和主体的小程序,可申请微信人脸核验接口。目前微信人脸核身接口已改为线上自助申请方式,需按照如下图例指引,进行接口申请: 第一步:请通过mp.weixin.qq.com登录小程序账号在后台“功能-人脸核身”的路径,点击开通按钮—— [图片] 第二步:仔细查阅《人脸识别身份信息验证服务条款》后,点击“同意并下一步”—— [图片] 第三步:请正确填写服务信息,并上传该小程序类目下所要求的资质—— [图片] 第四步:请按照业务实际需求填写使用人脸接口的场景和用途—— [图片] 第五步:请完善测试信息和联系人—— [图片] 第六步:提交后请耐心等待1-3个工作日的审核期,审核结果将以站内信通知—— 如申请期间遇到问题,可联系腾讯工作邮箱 wx_city@tencent.com,将会有相关工作人员进一步指引。 三、接口文档: (一)接口描述 名称: wx.startFacialRecognitionVerify(OBJECT) 功能:请求进行基于生物识别的人脸核身 验证方式:在线验证 兼容版本: 一闪:android 微信7.0.22以上版本, iOS 微信7.0.18以上版本 建议在微信官网升级至最新版本 (二)参数说明 1、OBJECT参数说明: 参数 类型 必填 说明 name String 是 姓名 idCardNumber String 是 身份证号码 success Function 否 调用成功回调 fail Function 否 调用失败回调 complete Function 是 调用完成回调(成功或失败都会回调) 2、CALLBACK返回参数 参数 类型 说明 errMsg String 错误信息 errCode Number 错误码 verifyResult String 本次认证结果凭据,第三方可以选择根据这个凭据获取相关信息 注 1:传递用户姓名和身份证有两种方式 业务方没有用户实名信息,用户需要在前端填写身份证和姓名,那么前端直接通过jsapi 调用传递 name 和 idCardNumber。 业务方已经有用户实名信息,后台通过微信提供的 api(详情见文档后面“上传姓名身份证后台 api”)上传用户身份证姓名和身份证,api 返回 user_id_key 作为凭证传给前端,前端再调用 jsapi,用户姓名、身份证信息不需要经过前端,参数只需要传递 userIdKey。Tips:使用该功能需要小程序基础库版本号>=1.9.3。 3、回调结果说明 回调结果请参考以下释义: [图片] [图片] [图片] 4、示例代码 [图片] [图片] (三)上传用户姓名身份证的后台api 1、API说明 1.1说明 业务方上传用户姓名和身份证,获取用户凭证,把凭证给到前端通过 jsapi 调用。 Tips :使用该功能需要小程序基础库版本号>=1.9.3。 1.2请求URL https://api.weixin.qq.com/cityservice/face/identify/getuseridkey?access_token={ac cess_token} 1.3请求方式 POST 2、请求数据格式 [代码]Json { "name" : “张三”, "id_card_number" : "452122xxxxxxx43215" } [代码] 请求示例 [代码]#!/bin/bash TOKEN='xxxxxxxxxxxx' URL='https://api.weixin.qq.com/cityservice/face/identify/getuseridkey' JSON='{ "name": "张三", "id_card_number": "452344xxxxxxxxxxxxx234"}' curl "${URL}?access_token=${TOKEN}" -d "${JSON}" [代码] 参数说明 json 字段 中文显示 是否必传 name 姓名 是 id_card_number 身份证号码 是 out_seq_no 业务方唯一流水号 否 3、返回数据 参数 类 型 说明 errcode int 错误码 errmsg string 错误信息 user_id_key string 用于后台交互表示用户姓名、身份证的凭证 expires_in uint32 user_id_key 有效期,过期需重新获取 [代码]{ "errcode" : 0, "errmsg" : "ok", "user_id_key" : "id_key_xxxx", "expires_in": 3600 } [代码] 4、后台消息推送 如果业务方传入out_seq_no,核身完成后会通过消息推送回调给业务方的服务器,如果回调业务方失败,会在5s尽力推送,超过5s不再推送。 参数说明 参数 类 型 说明 ToUserName string 小程序原始ID FromUserName string 事件消息openid CreateTime uint32 消息推送时间 MsgType string 消息类型 Event string 事件类型 openid string 核身用户的openid out_seq_no string 业务方唯一流水号 verify_result string 核身返回的加密key(凭据) 返回示例 [代码]{ "ToUserName": "gh_81fxxxxxxxx", "FromUserName": "oRRn15NUibBxxxxxxxxx", "CreateTime": 1703657835, "MsgType": "event", "Event": "face_identify", "openid": "oRRn15NUibBxxxxxxxxx", "out_seq_no": "test1234", "verify_result": "XXIzTtMqCxwOaawoE91-VNGAC3v1j9MP-5fZJxv0fYT4aGezzvYlUb-n6RWQa7XeJpQo0teKj8mGE4ZcRe1JI3GqzADBYORBu613rKjKAFfEXTXw_bu1bs7MnmPOpguS" } [代码] 四、再次获取核验结果api 此接口是前端完成人脸核身后,基于前端返回的凭据,通过后台api再次进行核验结果和身份信息的校验,有助于提高安全性,请务必接入! 前端获取结果不可信,存在被篡改的风险,为了保障请求结果安全性,请务必对identify_ret、id_card_number_md5、name_utf8_md5字段进行校验! (一)API说明 1、说明 人脸核身之后,开发者可以根据jsapi返回的verify_result向后台拉取当次认证的结果信息。 2、请求URL https://api.weixin.qq.com/cityservice/face/identify/getinfo?access_token={access_token} 3、请求方式 POST 4、请求格式 json (二)请求数据说明 1、请求 参数 类型 是否必填 描述 verify_result String 是 jsapi返回的加密key(凭据) 2、数据返回 HTTP 头如下 Date: Mon, 06 Feb 2017 08:12:58 GMT Content-Type: application/json; encoding=utf-8 Content-Length: 85 Connection: close json示例 [代码]{ "errcode" : 0, [代码] [代码]"errmsg" : "ok", "identify_ret" : 0, "identify_time" : 1486350357 "validate_data": "8593" [代码] [图片] (三)返回参数说明 1、返回参数 注:errcode和identify_ret同时为0,代表本次认证成功。 参数 类型 描述 errcode int 错误码, 0表示本次api调用成功 errmsg string 本次api调用的错误信息 identify_ret int 人脸核身最终认证结果 identify_time uint32 认证时间 validate_data string 用户读的数字(如是读数字) openid string 用户openid user_id_key string 用于后台交互表示用户姓名、身份证的凭证 finish_time uint32 认证结束时间 id_card_number_md5 string 身份证号的md5(最后一位X为大写) name_utf8_md5 string 姓名MD5 2、错误码对应信息 errcode 备注 84001 非法identity_id 84002 用户信息过期 84003 用户信息不存在 五、小程序辅助接口:检查设备是否支持人脸检测 1、接口名称 接 口 :wx.checkIsSupportFacialRecognition(OBJECT) 功能:检查设备是否支持人脸检测 2、接口说明和使用 小程序调用该接口,可以检测当前手机设备是否具备支持人脸检测的能力,可与以上接口分开使用,为了用户体验,建议调用后对手机设备不支持的用户做对应功能处理。 3、接口说明和使用 01 OBJECT 参数说明: 参数 类型 是否必填 描述 success Function 否 调用成功回调 fail Function 否 调用失败回调 complete Function 是 调用完成回调(成功或失败都会回调) checkAliveType Number 否 人脸核验的交互方式,默认读数字(见表 2) 表 2:checkAliveType 的值和对应的解释: 参数 解释 2 先检查是否可以屏幕闪烁,不可以则自动为读数字 02 CALLBACK 返回参数 参数 类型 说明 errMsg Boolean 错误信息 errCode Number 错误码 03 回调结果说明 回调类型 ErrCode 说明 sucess 0 支持人脸采集 fail 10001 不支持人脸采集:设备没有前置摄像头 fail 10002 不支持人脸采集:没有下载到必要模型 fail 10003 不支持人脸采集:后台控制不支持 回调结果说明仅对Android生效,iOS不返回errcode。 04 示例代码 [图片] 六、安全性说明 为保障业务可用性以及安全性,请详细研读微信人脸核身接口相关基础说明及安全说明文档:https://docs.qq.com/doc/DTFB0YWFIdGV6amly 备注:如开发中遇到任何疑问,可以点击此处通过社区反馈,将有工作人员跟进回复。 七、案例展示及补充说明 安徽医科大学第二附属医院,微信人脸核验登录: 安徽医科大学第二附属医院,是三级甲等综合医院。其小程序为用户提供挂号、门诊费用、住院费用、检查报告、体检等医疗服务,同时也提供停车、餐饮等便民服务,是医疗小程序中完整的案例。 小程序使用了微信人脸核验能力作为登录的核验。满足医院管理要求,也满足国家对于实名就医的管理规则。 案例实现的截图效果如下: [图片] [图片] 针对近期少数小程序方面反馈的两类问题,也在本课程进行补充说明。 1、本接口的开放范围,即:可支持的主体类目,是否可以扩大? 说明:基于本接口整体使用范围的评估、相关法规的参考、监管策略的理解执行等,暂时未立刻进行扩大开放范围的工作。 但我们会持续基于不同行业的法规、政策及监管要求等,逐一进行研究考量,以便确认如何扩大开放范围。 2、小程序如果涉及用户本人的生物特征采集,(如本人人脸照片、人脸视频),或涉及采集用户本人生物特征信息并开展人脸核验功能,则存在被驳回的情况? 说明:近两年“人脸识别”技术在社会上掀起了热潮。人脸识别虽然作为摆脱“中间媒介”或“承载载体”的一种直接技术手段,解决了部分政务、交通、医疗、零售等证明“操作者是本人”的问题,但也因此,引入了新的更大的安全风险。 一是,虚假安全风险。 身份认证领域的安全三因素包括“我知道什么”、“我拥有什么”、“我的特征是什么”,通用的安全做法,是要双因素认证(2FA),人脸识别技术如仅凭“我的特征是什么”这一个因素,则容易被攻破或利用。表象给用户以安全的感觉,但实际并不能达到安全效果。 二是,信息泄漏的风险。 越来越多的组织或个人,在并非必需用户敏感信息、生物特征的情况下,采集并存储此类信息。在信息加密、传输、存储过程中,容易暴漏更多的网络节点,使得此类信息有更大的风险被网络黑客拦截、窃听、窃取,或直接被脱库。 三是,消除风险的难度大。 以往基于“中间媒介”或“承载载体”的方式,如出现丢失、被冒用、恶意盗用等风险,可以通过挂失、更换、使用新载体或新媒介等方式,快速排除一定的风险。C端主动,B端主动,都能解决一部分问题。但人脸识别做为更直接的方式,一旦出现冒用、盗用,受害者将面临更大的财产及人生安全风险,且C端用户更多时候无法主动消除风险。 基于以上问题风险,加之国家出台《网络安全法》、《用户隐私保护条例》等法律法规标准,网信办、公安部、工信部及市场监管总局等四部委发起的app获取隐私整治,结合平台安全、用户敏感隐私信息保护要求及监管,针对部分暂无相关法规或要求,需要采集或生物认证方式进行身份核验的,或以“追热点”或“尝鲜”为目的,采集用户生物特征或进行身份核验的,进行严格审核,必要时不予以支持。
星期二 18:50