微信小程序(登录、分享、支付)

一、微信优势

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)
})
}
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JackieChan_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值