一、微信优势
1、什么场景下使用微信分享
● 线下推广、线上传播
● 分享渠道:h5、小程序、APP
● 分享方式:微信好友、朋友圈、QQ好友、QQ空间、微博
2、微信分享带来的收益
● 拉新
● 留存
● 提升用户的粘性
● 品牌传播
3、微信分享的好处
● 标题直观
● 内容清晰
● 醒目的logo
● 统一的分享外观, 用户体验非常好
二、微信公众号h5网页授权登录
1、初始化express项目
$ cnpm i express-generator -g
$ express -h
$ express sharepay_server
$ cnpm i && node bin/www || pm2 start bin/www
2、安装依赖
cnpm i request memory-cache create-hash --save-dev
// request 请求
// memory-cache 内存缓存
// create-hash 加密算法
3、微信公众号网页授权流程
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
● 通过此链接用户同意授权, 获取code
// appid 公众号的唯一标识
// redirect_uri 授权后重定向的回调链接地址
// response_type 返回类型, 固定写入code
// scope 应用授权作用域
// 【snsapi_base 不弹出授权页面,直接跳转,只能获取用户openid】
// 【snsapi_userinfo 弹出授权页面, 可通过openid拿到一些用户信息】
● 同意授权后, 页面将跳转到redirect_uri/?code=CODE&state=STATE
// code 作为换取access_token的票据
● 紧接着通过code换取网页授权的access_token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
// appid 公众号的唯一标识
// secret 公众号的秘钥
// code 换取access_token的票据
// grant_type 固定写入authorization_code
// 返回值
// access_token 网页授权接口调用凭证
// expires_in access_token接口调用凭证超时时间(s)
// refresh_token 用户刷新access_token
// openid 用户唯一标识
// scope 用户授权的作用域
● 获取用户信息确保scope为snsapi_userinfo, 根据access_token和openid拉取用户信息
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
// 入参
// access_token 网页授权接口调用凭证
// openid 用户的唯一标识
// lang 语言
4、JS-SDK签名算法
● 首先需要获取token
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
● 然后获取ticket
https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
● 加密算法 create-hash
5、Node连接Mongodb
$ cnpm i mongodb --save-dev
/**
* @description Mongodb公共文件,统一操作数据库
*/
let MongoClient = require(‘mongodb’).MongoClient
let util = require(‘…/…/util/util’)
let url = ‘mongodb://127.0.0.1:27017/sharepayServer’
exports.query = (data, table) => {
return new Promise((resolve, reject) => {
connect((dbase, db) => {
dbase.collection(table).find(data).toArray((err, res) => {
if(err){
throw err;
}else{
db.close();
resolve(util.handleSuccess(res))
}
})
})
})
}
exports.insert = (data, table) => {
return new Promise((resolve, reject) => {
connect((dbase, db) => {
dbase.collection(table).insertOne(data, (err, res) => {
if (err) {
throw err;
} else {
db.close();
resolve(util.handleSuccess(res))
}
})
})
})
}
function connect(callbck) {
MongoClient.connect(url, (err, db) => {
if(err) throw err;
let dbase = db.db('sharepayServer');
callbck(dbase, db);
})
}
6、服务端代码
// config
module.exports = {
wx: {
appId: ‘wx02432db3c653775b’,
appSecret: ‘08cf50652e306a9e6d038a400f552bf1’
},
mp: {
appId: ‘wx2d964cda16732f19’,
appSecret: ‘7f03a96c1a8ba04449b2a3f9431a9438’
}
}
// util
module.exports = {
// 生成随机数
createNonceStr() {
return Math.random().toString(36).substr(2, 15)
},
// 生成时间戳
createTimeStamp() {
return new Date().getTime() / 1000 + ‘’
},
// Object转换成json并进行排序
raw(args) {
let keys = Object.keys(args).sort()
let obj = {}
keys.forEach((key) => {
obj[key] = args[key]
})
// 将对象转换为&分割的参数
let val = ‘’
for (let k in obj) {
val += ‘&’ + k + ‘=’ + obj[k]
}
return val.substr(1)
},
// 对请求结果进行统一封装处理
handleResponse(err, response, body) {
if(!err && response.statusCode == ‘200’) {
let data = JSON.parse(body)
if(data && !data.errcode) {
return this.handleSuccess(data)
} else {
return this.handleFail(data.errmsg, data.errcode)
}
} else {
return this.handleFail(err, 10009)
}
},
handleSuccess(data = ‘’) {
return {
code: 0,
data,
msg: ‘’
}
},
handleFail(msg = ‘’, code = 10001) {
return {
code,
data: ‘’,
msg
}
}
}
// 依赖引入
let express = require(‘express’)
let cache = require(‘memory-cache’)
let createHash = require(‘create-hash’)
let config = require(‘./config’).wx
let common = require(‘…/common/index’)
let dao = require(‘…/common/db’)
let util = require(‘…/…/util/util’)
let router = express.Router()
// 用户授权重定向
router.get('/redirect', (req, res) => {
// 获取重定向地址
let redirectUrl = req.query.url
// 将重定向url进行缓存,便于公用
cache.put('redirectUrl', redirectUrl)
let scope = req.query.scope
let callback = 'http://m.imooc.com/api/wechat/getOpenId'
let authorizeUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${config.appId}&redirect_uri=${callback}&response_type=code&scope=${scope}&state=STATE#wechat_redirect`
res.redirect(authorizeUrl)
})
// 根据code获取用户的openId
router.get('/getOpenId', async (req, res) => {
let code = req.query.code
if(!code) {
util.handleFail('当前未获取到code码')
} else {
let result = await common.getAccessToken(code)
if(result.code == 0) {
let data = result.data
let expire_time = 1000 * 60 * 60 * 2
cache.put('access_token', data.access_token, expire_time)
cache.put('openId', data.openid, expire_time)
res.cookie('openId', data.openid, { maxAge: expire_time })
// 查询数据库看看有没有openid信息
let openId = data.openid
let userRes = await dao.query({'openid': openId}, 'users')
if (userRes.code == 0) {
if (userRes.data.length > 0) {
let redirectUrl = cache.get('redirectUrl')
res.redirect(redirectUrl)
} else {
let userData = await common.getUserInfo(data.access_token, openId)
let insertData = await dao.insert(userData.data,'users')
if (insertData.code == 0) {
let redirectUrl = cache.get('redirectUrl');
res.redirect(redirectUrl);
} else {
res.json(insertData);
}
}
} else {
res.json(userRes)
}
} else {
res.json(result)
}
}
})
// 拉取用户信息
router.get('/getUserInfo', async (req, res) => {
let access_token = cache.get('access_token')
let openId = cache.get('openId')
let result = await common.getUserInfo(access_token, openId)
res.json(result)
})
// jssdk签名算法
router.get('/jssdk', async (req, res) => {
let url = req.query.url
let result = await common.getToken()
if (result.code == 0) {
let tocken = result.data.access_token
cache.put('token', tocken)
let ticket = await common.getTicket(tocken)
if (ticket.code == 0) {
let data = ticket.data
let params = {
noncestr: util.createNonceStr(),
jsapi_ticket: data.ticket,
timestamp: util.createTimeStamp(),
url
}
let str = util.raw(params)
// 加密算法 create-hash
let sign = createHash('sha1').update(str).digest('hex')
res.json(util.handleSuccess({
appId: config.appId, // 必填,公众号的唯一标识
timestamp: params.timestamp, // 必填,生成签名的时间戳
nonceStr: params.nonceStr, // 必填,生成签名的随机串
signature: sign,// 必填,签名
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData',
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareQZone',
'chooseWXPay'
] // 必填,需要使用的JS接口列表
}))
}
}
})
module.exports = router
/**
* @description 微信接口统一封装处理
*/
let request = require(‘request’)
let config = require(‘…/pay/config’).wx
let util = require(‘…/…/util/util’)
exports.getAccessToken = (code) => {
let tokenUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${config.appId}&secret=${config.appSecret}&code=${code}&grant_type=authorization_code`
return new Promise((resolve, reject) => {
request.get(tokenUrl, (err, response, body) => {
let result = util.handleResponse(err, response, body)
resolve(result)
})
})
}
exports.getUserInfo = (access_token, openId) => {
let userinfo = `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openId}&lang=zh_CN`
return new Promise((resolve, reject) => {
request.get(userinfo, (err, response, body) => {
let result = util.handleResponse(err, response, body)
resolve(result)
})
})
}
// 获取基础接口的token
exports.getToken = () => {
let token = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.appId}&secret=${config.appSecret}`
return new Promise((resolve, reject) => {
request.get(token, (err, response, body) => {
let result = util.handleResponse(err, response, body)
resolve(result)
})
})
}
// 根据token获取ticket
exports.getTicket = (token) => {
let ticket = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=jsapi`
return new Promise((resolve, reject) => {
request.get(ticket, (err, response, body) => {
let result = util.handleResponse(err, response, body)
resolve(result)
})
})
}
7、客户端代码
// API
export default {
wechatRedirect:‘/api/wechat/redirect?url=http%3A%2F%2Fm.imooc.com%2F%23%2Findex&scope=snsapi_userinfo’,
wechatConfig:‘/api/wechat/jssdk’,
getUserInfo:‘/api/wechat/getUserInfo’,
payWallet: ‘/api/wechat/pay/payWallet’
}
import API from ‘./api/index’
import wx from ‘weixin-js-sdk’
import util from ‘./util/index’
export default {
name: ‘app’,
mounted () {
this.checkUserAuth()
},
methods: {
// 检查用户是否授权过
checkUserAuth () {
let openId = this.KaTeX parse error: Expected 'EOF', got '}' at position 170: … } }̲, // 获取…http.get(API.wechatConfig+‘?url=’+location.href.split(‘#’)[0]).then(function(response){
let res = response.data;
if(res.code == 0){
let data = res.data;
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: data.appId, // 必填,公众号的唯一标识
timestamp: data.timestamp, // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature,// 必填,签名
jsApiList: data.jsApiList // 必填,需要使用的JS接口列表
})
wx.ready(()=>{
util.initShareInfo(wx);
})
}
})
}
}
}
三、小程序登录分享
1、小程序分享的好处
● 体验友好
● 利于传播
● 只能分享给好友
● 不能分享第三方应用
2、小程序公共机制
目录结构
|- assets 【静态资源】
|— images 【图片】
|— wxss 【公共样式】
|- env 【环境配置*】
|— index.js
|- http 【服务请求*】
|— api.js 【dao层api地址】
|— request.js 【fetch请求封装】
|- pages 【页面】
|- utils 【工具*】
|— router.js 【通用路由跳转】
|— store.js 【通用本地存储】
|— util.js 【通用工具方法】
|- app.js 【小程序入口文件*】
|- app.json 【小程序入口配置】
|- app.wxss 【小程序全局样式】
// env
module.exports = {
mockApi: ‘’,
Dev: {
baseApi: ‘http://localhost:3000’
},
Test: {},
Slave: {},
Prod: {}
}
// http-api
module.exports = {
getSession: ‘/api/mp/getSession’,
login: ‘/api/mp/login’,
payWallet: ‘/api/mp/pay/payWallet’
}
// http-request
/**
* @description 公共请求方法
*/
let store = require('../utils/store.js')
let system = store.getSystemInfo();
const clientInfo = {
'clientType': 'mp',
'appnm': 'share_pay',
'model': system.model,
'os': system.system,
'screen': system.screenWidth + '*' + system.screenHeight,
'version': App.version,
'channel': 'miniprogram'
}
const errMsg = '服务异常,请稍后重试';
module.exports = {
fetch: (url, data = {}, option = {}) => {
let { loading = true, toast = true, isMock = false, method = 'get' } = option
return new Promise((resolve, reject) => {
let env = isMock ? App.config.mockApi : App.config.baseApi
wx.request({
url: env + url,
data,
method,
header: {
'clientInfo': JSON.stringify(clientInfo)
},
success: (result) => {
let res = result.data // {code:0,data:'',message:''}
if (res.code == 0) {
if (loading) {
wx.hideLoading();
}
resolve(res.data)
} else {
if (toast) {
wx.showToast({
mask: true,
title: res.messsage,
icon: 'none'
})
} else {
wx.hideLoading()
}
reject(res)
}
},
fail: (e = { code: -1, msg: errMsg, errMsg }) => {
let msg = e.errMsg;
if (msg == 'request:fail timeout') {
msg = '服务请求超时,请稍后处理'
}
wx.showToast({
title: msg,
icon: 'none'
})
reject(e)
}
})
})
}
}
// util-router.js
/**
* @description 通用的路由跳转文件
*/
const routerPath = {
‘index’: ‘/pages/index/index’,
‘pay’: ‘/pages/pay/pay’,
‘activity’: ‘/pages/activity/activity’
}
module.exports = {
// 页面跳转
push(path, option = {}) {
if (typeof path == 'string') {
option.path = path
} else {
option = path
}
// 获取url地址
let url = routerPath[option.path]
let { query = {}, openType, duration } = option
let params = this.parse(query)
if (params) {
url += '?' + params
}
duration ? setTimeout(() => {
this.to(openType, url)
}, duration) : this.to(openType, url)
},
to(openType, url) {
let obj = { url }
if (openType == 'redirect') {
wx.redirectTo(obj)
} else if (openType == 'reLaunch') {
wx.reLaunch(obj)
} else if (openType == 'back') {
wx.navigateBack({
delta: 1
})
} else {
wx.navigateTo(obj)
}
},
parse(data) {
let arr = []
for (let key in data) {
arr.push(key + '=' + data[key])
}
return arr.join('&')
}
}
// util-store.js
/**
* @description Storage通用存储文件定义
*/
const STORAGE_KEY = 'share_pay'
module.exports = {
setItem(key, value, module_name) {
if (module_name) {
let module_name_info = this.getItem(module_name);
module_name_info[key] = value;
wx.setStorageSync(module_name, module_name_info)
} else {
wx.setStorageSync(key, value)
}
},
// 获取值
getItem(key, module_name) {
if (module_name) {
let val = this.getItem(module_name);
if (val) return val[key];
return '';
} else {
return wx.getStorageSync(key);
}
},
// 删除或者清空值
clear(key) {
key ? wx.removeStorageSync(key) : wx.clearStorageSync();
},
getSystemInfo() {
return wx.getSystemInfoSync();
}
}
// app.js
/**
* @description 小程序入口
*/
let Api = require(‘./http/api’)
let request = require(‘./http/request’)
let router = require(‘./utils/router’)
let config = require(‘./env/index’)
let env = ‘Dev’
App.version = '1.0.0' // 开发版本
App.config = config[env] // 根据环境变量获取对应的配置信息
App.config.env = env
App.config.mockApi = config.mockApi
App({
Api,
router,
get: request.fetch,
post: (url, data, option) => {
option.method = 'post'
return request.fetch(url, data, option)
},
onLaunch: function () {
},
globalData: {
userInfo: null
}
})
3、小程序授权登录及分享
● 根据userId/openId判断当前用户是否登录
● 调用login获取到code
● 调用服务器端根据code换取openid
● 通过用户授权获取用户信息, 存入到后台数据库
const app = getApp()
let store = require(‘./…/…/utils/store.js’)
let Api = app.Api
let $router = app.router
Page({
data: {
userId: store.getItem(‘userId’)
},
onLoad: function () {
// 判断用户是否登录
if (!this.data.userId) {
this.getSession()
}
},
// 获取登录code, 换取openid
getSession() {
wx.login({
success: (res) => {
if (res.code) {
app.get(Api.getSession, {
code: res.code
}).then(res => {
store.setItem(‘openId’, res.openid);
}).catch((err) => {
console.log(‘error:’ + res.message)
})
}
}
})
},
// 获取用户信息
getUserInfo(e) {
let userInfo = e.detail.userInfo
userInfo.openid = store.getItem(‘openId’)
app.get(Api.login, {
userInfo
}).then(res => {
store.setItem(‘userId’, res.userId)
this.setData({
userId: res.userId
})
})
},
recharge() {
$router.push(‘pay’)
},
activity() {
$router.push(‘activity’)
},
onShareAppMessage() {
return {
title: ‘欢迎体验分享’,
path: ‘/pages/index/index’,
imageUrl: ‘/assets/images/share_mp_logo.png’
}
}
})
4、服务端代码
let express = require(‘express’)
let router = express.Router()
let request = require(‘request’)
let config = require(‘./config’)
let util = require(‘./…/…/util/util’)
let dao = require(‘./…/common/db’)
config = Object.assign({}, config.mp)
// 获取session接口
router.get('/getSession', (req, res) => {
let code = req.query.code
if (!code) {
res.json(util.handleFail('code不能为空', 10001))
return
}
let sessionUrl = `https://api.weixin.qq.com/sns/jscode2session?appid=${config.appId}&secret=${config.appSecret}&js_code=${code}&grant_type=authorization_code`
request(sessionUrl, (err, response, body) => {
let result = util.handleResponse(err, response, body)
res.json(result)
})
})
// 小程序授权登录
router.get('/login', async (req, res) => {
let userInfo = JSON.parse(req.query.userInfo)
if (!userInfo) {
res.json(util.handleFail('用户信息不能为空', 10002))
} else {
// 查询当前用户是否已经注册
let userRes = await dao.query({ openid: userInfo.openid }, 'users_mp')
if (userRes.code == 0) {
if (userRes.data.length > 0) {
res.json(util.handleSuccess({
userId: userRes.data[0]._id
}))
} else {
let insertData = await dao.insert(userInfo, 'users_mp')
if (insertData.code == 0) {
let result = await dao.query({ openid: userInfo.openid }, 'users_mp');
res.json(util.handleSuccess({
userId: result.data[0]._id
}))
} else {
res.json(insertData)
}
}
} else {
res.json(userRes)
}
}
})
module.exports = router
四、小程序云登录
1、云开发好处
● 节约开发和运维成本
● 关注核心功能开发
● 和普通小程序开发一致
● 拥有微信更多的能力
2、调用云函数登录
// 云函数 - getOpenId
const cloud = require(‘wx-server-sdk’)
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
return {
event,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
}
}
// 云函数 - login
const cloud = require('wx-server-sdk')
cloud.init()
// 连接数据库
const db = cloud.database()
const _users = db.collection('users')
exports.main = async (event, context) => {
// 获取上下文
const wxContext = cloud.getWXContext()
// 判断用户是否存在
const result = await _users.where({
openid: wxContext.OPENID
}).limit(1).get()
if(result.data.length == 0) {
// 如果用户不存在, 则需要插入到数据库然后返回userId(_id)
await _users.add({
data: {
...event.userInfo,
openid: wxContext.OPENID
}
})
// 再次查询
const result = await _users.where({
openid: wxContext.OPENID
}).limit(1).get()
return {
userId: result.data[0]._id
}
} else {
// 用户存在, 则直接返回userId
return {
userId: result.data[0]._id
}
}
}
const app = getApp()
let store = require(‘./…/…/utils/store.js’)
let $router = app.router
Page({
data: {
userId: store.getItem(‘userId’)
},
onLoad() {
this.getOpenId()
},
getOpenId() {
wx.cloud.callFunction({
name: 'getOpenId'
}).then(res => {
let { openid } = res.result
store.setItem('openId', openid)
})
},
// 获取用户信息
getUserInfo(e) {
// 获取用户信息
let userInfo = e.detail.userInfo
// 调用云函数
wx.cloud.callFunction({
name: 'login',
data: {
userInfo
}
}).then(res => {
store.setItem('userId', res.result.userId)
this.setData({
userId: res.result.userId
})
})
},
recharge() {
$router.push('pay')
},
activity() {
$router.push('activity')
},
onShareAppMessage() {
return {
title: '欢迎体验分享',
path: '/pages/index/index',
imageUrl: '/assets/images/share_mp_logo.png'
}
}
})
五、小程序支付
1、前沿
● 微信认证
● 微信支付认证
○ 注册商户号/关联商户号
○ H5配置支付授权目录
○ 非h5 授权AppId
○ 设置API秘钥
● https证书
2、支付流程
前端
● https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html
后端
● 拼接常规参数
● 生成签名
● 拼接xml数据
● 调用统一下单接口
● 获取预支付ID: prepay_id
● 生成支付SDK
● 定义回调接口, 接收微信支付消息
3、服务端代码
// config.js
// 商户号 - 用于支付
mch:{
mch_id: ‘’, // 商户号id
key:‘’ // 秘钥
}
// 小程序支付
router.get(‘/pay/payWallet’, (req, res) => {
let openId = req.query.openId // 用户openid
let appId = config.appId;
let attach = “thanks use my pay” // 附加数据
let body = “welcome” // 详细内容
let total_fee = req.query.money // 支付费用(分)
let notify_url = “http://localhost:3000/api/mp/pay/callback” // 通知地址
let ip = “123.57.2.144” //ip地址
wxpay.order(appId, attach, body, openId, total_fee, notify_url, ip).then((result) => {
res.json(util.handleSuccess(result));
}).catch((result) => {
res.json(util.handleSuccess(result.toString()))
})
res.json(util.handleSuccess())
})
// 支付回调通知
router.get('/pay/callback', (req, res) => {
res.json(util.handleSuccess())
})
/**
* @description 微信小程序、H5通用支付封装
*/
let config = require(‘./…/pay/config’)
let request = require(‘request’)
let util = require(‘…/…/util/util’)
let createHash = require(‘create-hash’)
let xml = require(‘xml2js’)
config = config.mch
module.exports = {
order: (appid, attach, body, openid, total_fee, notify_url, ip) => {
return new Promise((resolve, reject) => {
let nonce_str = util.createNonceStr() // 随机数
let out_trade_no = util.getTradeId('mp') // 交易订单号
// 支付前需要先获取支付签名
let sign = this.getPrePaySign(appid, attach, body, openid, total_fee, notify_url, ip, nonce_str, out_trade_no)
// 通过参数和签名组装xml数据
let sendData = this.wxSendData(appid, attach, body, openid, total_fee, notify_url, ip, nonce_str, out_trade_no, sign)
// 调用统一下单接口
let self = this
let url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
request({
url,
method: 'POST',
body: sendData
}, (err, response, body) => {
if (!err && response.statusCode == 200) {
// 通过xml2js来处理xml的数据
xml.parseString(body.toString('utf-8'), (error, res) => {
if (!error) {
// 获取到转换后的内容
let data = res.xml
if (data.return_code[0] == 'SUCCESS' && data.result_code[0] == 'SUCCESS') {
// 获取预支付的ID
let prepay_id = data.prepay_id || []
// 返回小程序端需要获取的参数值
let payResult = self.getPayParams(appid, prepay_id[0])
resolve(payResult)
}
}
})
} else {
resolve(util.handleFail(err))
}
})
})
},
// 生成预支付的签名
getPrePaySign: (appid, attach, body, openid, total_fee, notify_url, ip, nonce_str, out_trade_no) => {
let params = {
appid,
attach,
body,
mch_id: config.mch_id,
nonce_str,
notify_url,
openid,
out_trade_no,
spbill_create_ip: ip,
total_fee,
trade_type: 'JSAPI'
}
let string = util.raw(params) + '&key=' + config.key
let sign = createHash('md5').update(string).digest('hex')
return sign.toUpperCase()
},
// 签名成功后 ,根据参数拼接组装XML格式的数据,调用下单接口
wxSendData: (appid, attach, body, openid, total_fee, notify_url, ip, nonce_str, out_trade_no, sign) => {
let data = '<xml>' +
'<appid><![CDATA[' + appid + ']]></appid>' +
'<attach><![CDATA[' + attach + ']]></attach>' +
'<body><![CDATA[' + body + ']]></body>' +
'<mch_id><![CDATA[' + config.mch_id + ']]></mch_id>' +
'<nonce_str><![CDATA[' + nonce_str + ']]></nonce_str>' +
'<notify_url><![CDATA[' + notify_url + ']]></notify_url>' +
'<openid><![CDATA[' + openid + ']]></openid>' +
'<out_trade_no><![CDATA[' + out_trade_no + ']]></out_trade_no>' +
'<spbill_create_ip><![CDATA[' + ip + ']]></spbill_create_ip>' +
'<total_fee><![CDATA[' + total_fee + ']]></total_fee>' +
'<trade_type><![CDATA[JSAPI]]></trade_type>' +
'<sign><![CDATA[' + sign + ']]></sign>' +
'</xml>'
return data
},
// 生成前端需要的sdk的配置
getPayParams: (appId, prepay_id) => {
let params = {
appId,
timeStamp: util.createTimeStamp(),
nonceStr: util.createNonceStr(),
package: 'prepay_id=' + prepay_id,
signType: 'MD5'
}
let paySign = util.getSign(params, config.key)
params.paySign = paySign
return params
}
}
六、h5支付
https://www.yuque.com/robinson/fe-guide/gt9hb4
1、准备
● h5支付后台可以是http/https, 需配置支付授权目录
● 小程序所有的接口都必须是https, 无需配置授权目录
● 小程序云开发只有云函数, 无需配置授权目录
● 域名、服务器、证书准备
● 微信认证、企业/个体主体认证
● 支付认证, 开通商户平台
● 公众号、小程序、小程序云绑定商户号
2、服务端代码
// 微信支付
router.get(‘/pay/payWallet’, (req, res) => {
let openId = req.cookies.openId
let appId = config.appId
// 商品简单描述
let body = ‘welcome’
// 钱数, 如果是post请求则使用req.body获取参数
let total_fee = req.query.money
// 微信成功回调地址, 用于保存用户支付订单信息
let notify_url = ‘http://m.imooc.com/api/wechat/pay/callback’
// 通过微信支付认证的商户ID
let mch_id = config.mch_id
// 附加数据
let attach = ‘123’
// 调用微信支付API的ip地址
let ip = ‘123.57.2.144’
// 封装好的微信下单接口
wxpay
.order(appId, attach, body, mch_id, openId, total_fee, notify_url, ip)
.then(result => {
res.json(util.handleSuccess(result))
})
.catch((result) => {
res.json(util.handleFail(result))
})
})
// 支付成功的回调
router.post('/pay/callback', (req, res) => {
xml.parseString(req.rawBody.toString('utf-8'), async(error, xmlData) => {
if (error) {
logger.error(error);
res.send('fail')
return;
}
let data = xmlData.xml
let order = {
openId: data.openid[0],
totalFee: data.total_fee[0],
isSubscribe: data.is_subscribe[0],
orderId: data.out_trade_no[0],
transactionId: data.transaction_id[0],
tradeType: data.trade_type[0],
timeEnd: data.time_end[0]
}
// 插入订单数据
let result = await dao.insert(order, 'orders');
if (result.code == 0) {
// 向微信发送成功数据
let data = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></r' +
'eturn_msg></xml>';
res.send(data);
} else {
res.send('FAIl');
}
})
})
3、前端代码
this.KaTeX parse error: Expected '}', got 'EOF' at end of input: … _this.route.push(‘/index’);
} else if (res.errMsg == “chooseWXPay:fail”) {
alert(‘支付取消’);
}
}.bind(this),
cancel: function () {
alert(‘支付取消’);
}.bind(this),
fail: function (res) {
alert(res.message || ‘支付失败’);
}.bind(this)
})
}
});