該網(wǎng)站不安全,請不要輸入密碼 |
op=>operation: openid判斷是否登錄授權(quán) op2=>operation: 根據(jù)wx.login獲取code op3=>operation: 調(diào)用服務(wù)端根據(jù)code換取openid op4=>operation: 通過用戶授權(quán),獲取信息,存到數(shù)據(jù)庫 op->op2->op3->op4 復(fù)制代碼 如果你從來沒有閱讀過小程序登錄授權(quán)的文檔,建議你看一下下面的地址: 服務(wù)端官方文檔 客戶端文檔 |
onLoad() {
if(!this.data.userId) {
this.getSession()
}
},
getSession() {
wx.login({
success: (res) => {
if (res.code) {
app.get(Api.getSession, {
code: res.code
}).then(res => {
store.setItem('openid', res.openid)
})
}
}
})
}復(fù)制代碼
getUserInfo(e) {
let userInfo = e.detail.userInfo;
userInfo.openid = store.getItem('openid')
app.get(Api.login, {
userInfo
}).then(res => {
store.setItem('userId', res.data.userId)
this.setData({
userId: res.userId
})
})
}復(fù)制代碼
|
|
在 config 里面,定義公用的 appid 和 appsecret
module.exports = {
wx: {
appId: 'wx0ef10432747d8f57',
appsecret: 'cc47a6127687e999a1dffa756ff83c0e'
},
mp: {
appId: 'wx0691f1dcf6e5e231',
appSecret: 'c7ed875e338120f15f49476a6596eb4f'
}
}復(fù)制代碼
然后通過調(diào)用小程序 官方文檔 的接口,獲取到 appid 傳給客戶端
let express = require('express');
let router = express.Router();
let request = require('request');
let config = require('./config');
let uril = require('./../../util/index')
config = Object.assign({}, config.mp);
router.get('/getSession', (req, res) => {
let code = req.query.code
if (!code) {
res.json(uril.handleFail('code不能為空', 10001))
}
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)
})
})復(fù)制代碼
登錄接口 的編寫
// 小程序授權(quán)登錄
router.get('/login',async function(req,res){
let userInfo = JSON.parse(req.query.userInfo);
if (!userInfo){
// 如果接口沒有信息,則返回錯(cuò)誤信息
res.json(util.handleFail('用戶信息不能為空',10002))
}else{
// 查詢當(dāng)前用戶是否已經(jīng)注冊
let userRes = await dao.query({ openid: userInfo.openid},'users_mp');
if (userRes.code == 0){
// 如果已經(jīng)注冊,直接把查出來的信息返回給客戶端
if (userRes.data.length >0){
res.json(util.handleSuc({
userId: userRes.data[0]._id
}))
}else{
// 如果這個(gè)用戶之前沒有注冊,則在數(shù)據(jù)庫插入用戶信息
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.handleSuc({
userId: result.data[0]._id
}))
}else{
res.json(insertData);
}
}
}else{
res.json(userRes);
}
}
})復(fù)制代碼
上述代碼的 handleFail 和 handleResponse 是封裝的對數(shù)據(jù)的統(tǒng)一處理,如果有興趣,參見 github 地址。這里不展示代碼。 需要注意的是,這種實(shí)現(xiàn)方式,獲取 openid 的行為放在后端實(shí)現(xiàn)了。如果放在前端實(shí)現(xiàn)也可以,但是會(huì)相對比較麻煩一點(diǎn)。此時(shí),suerId就已經(jīng)在數(shù)據(jù)庫存儲(chǔ),并且在本地保存了,下次登錄的時(shí)候,如果有userId存在就不需要再次登錄了。 |
|
H5的登錄授權(quán)略有不同。如果用戶登錄授權(quán)頁面,發(fā)現(xiàn)該用戶沒有登錄授權(quán),則需要跳轉(zhuǎn)到授權(quán)頁面。 官方文檔 給出的流程如下: 1 第一步:用戶同意授權(quán),獲取code2 第二步:通過code換取網(wǎng)頁授權(quán)access_token3 第三步:刷新access_token(如果需要)4 第四步:拉取用戶信息(需scope為 snsapi_userinfo)5 附:檢驗(yàn)授權(quán)憑證(access_token)是否有效 在項(xiàng)目中代碼如下:(這里代碼沒有實(shí)現(xiàn)刷新access_token和拉取用戶信息) 頁面加載的時(shí)候,判斷是否已經(jīng)授權(quán)。
mounted(){
this.checkUserAuth();
},
methods:{
// 檢查用戶是否授權(quán)過
checkUserAuth(){
let openId = this.$cookie.get('openId');
if(!openId){
// 如果沒有登錄授權(quán),則跳轉(zhuǎn)到微信提供的跳轉(zhuǎn)頁面。
window.location.href = API.wechatRedirect;
}else{
// 如果用戶已經(jīng)授權(quán),則調(diào)用獲取微信配置信息接口
this.getWechatConfig();
}
},復(fù)制代碼
API.wechatRedirect: wechatRedirect:'/api/wechat/redirect?url=http%3A%2F%2Fm.51purse.com%2F%23%2Findex&scope=snsapi_userinfo',復(fù)制代碼 |
nodejs 對登錄授權(quán)回調(diào)接口的實(shí)現(xiàn)主要是拿到客戶端的請求參數(shù),請求微信提供的 接口
// 用戶授權(quán)重定向
router.get('/redirect',function (req,res) {
let redirectUrl = req.query.url, scope = req.query.scope, callback = 'http://m.51purse.com/api/wechat/getOpenId';
cache.put('redirectUrl', redirectUrl);
// 獲取到客戶端帶過來的數(shù)據(jù),請求微信接口
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);
})
復(fù)制代碼
當(dāng)用戶點(diǎn)擊 確認(rèn)授權(quán) 之后,會(huì)執(zhí)行跳轉(zhuǎn) callbacl:http://m.51purse.com/api/wechat/getOpenId 。而這個(gè)接口也是node端實(shí)現(xiàn)的,具體內(nèi)容如下:
// 用code換取access_token的方法
exports.getAccessToken = function(code){
let token_url = `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(token_url, function (err, response, body) {
let result = util.handleResponse(err, response, body);
resolve(result);
})
});
}
// 根據(jù)code獲取用戶的OpenId
router.get('/getOpenId',async function(req,res){
let code = req.query.code;
console.log("code:"+code);
if(!code){
res.json(util.handleFail('當(dāng)前未獲取到授權(quán)code碼'));
}else{
// 用code換取access_token
let result = await common.getAccessToken(code);
if(result.code == 0){
// 換取access_token成功
let data = result.data;
let expire_time = 1000 * 60 * 60 * 2;
// 往客戶端寫入cookie:openId
res.cookie('openId', data.openid, { maxAge: expire_time });
let openId = data.openid;
let userRes = await dao.query({ 'openid': openId },'users');
if (userRes.code == 0){
if (userRes.data.length>0){
// 從數(shù)據(jù)庫查找到用戶信息后,回調(diào)到客戶端的頁面
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){
// 從數(shù)據(jù)庫查找到用戶信息后,回調(diào)到客戶端的頁面
let redirectUrl = cache.get('redirectUrl');
res.redirect(redirectUrl);
}else{
// 返回錯(cuò)誤信息
res.json(insertData);
}
}
}else{
// 返回錯(cuò)誤信息
res.json(userRes);
}
}else{
// 返回錯(cuò)誤信息
res.json(result);
}
}
})
復(fù)制代碼
「注意」:上面的代碼為了簡單,刪除了一些不必要的代碼,如有興趣,訪問gitHub。 |
|
同樣,如果你沒有閱讀過微信H5開發(fā)的 官方文檔 ,建議你先閱讀。關(guān)于分享,你應(yīng)該閱讀以下內(nèi)容: 當(dāng)再次回調(diào)到頁面的時(shí)候,從cookie已經(jīng)拿到openId了??蛻舳藭?huì)繼續(xù)執(zhí)行下面的代碼。獲取到服務(wù)端返回的配置信息,從而初始化分享的功能。 在這之前,你需要 npm install wx-jssdk
// 這個(gè)信息統(tǒng)一定義在api.js中,這里為了方便,放在前面,便于查看。
API.wechatConfig: /api/wechat/jssdk
// 獲取微信配置信息
getWechatConfig(){
this.$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, // 開啟調(diào)試模式,調(diào)用的所有api的返回值會(huì)在客戶端alert出來,若要查看傳入的參數(shù),可以在pc端打開,參數(shù)信息會(huì)通過log打出,僅在pc端時(shí)才會(huì)打印。
appId: data.appId, // 必填,公眾號的唯一標(biāo)識
timestamp: data.timestamp, // 必填,生成簽名的時(shí)間戳
nonceStr: data.nonceStr, // 必填,生成簽名的隨機(jī)串
signature: data.signature,// 必填,簽名
jsApiList: data.jsApiList // 必填,需要使用的JS接口列表
})
wx.ready(()=>{
util.initShareInfo(wx);
})
}
})
}復(fù)制代碼
util/index.js 里面對分享的功能進(jìn)行了封裝。
export default {
//獲取瀏覽器地址欄參數(shù)值
getUrlParam(name){
let reg = new RegExp('(^|&)'+name+'=([^&]*)');
let r = window.location.search.substr(1).match(reg);
if(r!=null)return decodeURIComponent(r[2]);
},
initShareInfo(wx){
let shareInfo = {
title: 'xxxx', // 分享標(biāo)題
desc: 'xxxx', // 分享描述
link: 'http://m.51purse.com/#/index', // 分享鏈接,該鏈接域名或路徑必須與當(dāng)前頁面對應(yīng)的公眾號JS安全域名一致
imgUrl: '', // 分享圖標(biāo)
}
wx.onMenuShareAppMessage(shareInfo);
wx.onMenuShareTimeline(shareInfo);
wx.onMenuShareQQ(shareInfo);
wx.onMenuShareQZone(shareInfo);
// 下面兩種方法為新的方法,上面的方法將會(huì)被淘汰。
wx.updateAppMessageShareData(shareInfo);
wx.updateTimelineShareData(shareInfo);
}
}
復(fù)制代碼
nodejs端對 /wechat/jssdk 接口的實(shí)現(xiàn)如下:
// common.getToken()方法 獲取基礎(chǔ)接口的Token
exports.getToken = function(){
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, function (err, response, body) {
let result = util.handleResponse(err, response, body);
resolve(result);
})
})
}
----
router.get('/jssdk',async function(req,res){
let url = req.query.url;
let result = await common.getToken();
if (result.code == 0){
let token = result.data.access_token;
let params = {
// 生成隨機(jī)字符串
noncestr:util.createNonceStr(),
// 生成時(shí)間戳
timestamp:util.createTimeStamp(),
url
}
let str = util.raw(params);
console.log('str:::' + JSON.stringify(params))
let sign = createHash('sha1').update(str).digest('hex');
res.json(util.handleSuc({
appId: config.appId, // 必填,公眾號的唯一標(biāo)識
timestamp: params.timestamp, // 必填,生成簽名的時(shí)間戳
nonceStr: params.noncestr, // 必填,生成簽名的隨機(jī)串
signature: sign,// 必填,簽名
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData',
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareQZone',
'chooseWXPay'
] // 必填,需要使用的JS接口列表
}))
}
}else{
res.json(result);
}
})復(fù)制代碼
以上代碼主要獲得基礎(chǔ)的 token ,然后用基礎(chǔ) token 結(jié)合簽名、時(shí)間戳、隨機(jī)數(shù)等相關(guān)的參數(shù),返回給客戶端相應(yīng)的參數(shù)。 需要注意的是, 基礎(chǔ)token 和 accessToken 的區(qū)別。建議 參考文章 。 到此,微信H5接入jssdk實(shí)現(xiàn)分享就已經(jīng)完成了。 |
|
小程序支付前端流程
后端支付流程
支付的主要邏輯在服務(wù)端 下面把服務(wù)端的流程通過代碼的方式表述出來。首先在util中封裝了一些支付需要的公共方法
/**
* 公共函數(shù)定義
*/
let createHash = require('create-hash');
module.exports = {
// 生成隨機(jī)數(shù)
createNonceStr(){
return Math.random().toString(36).substr(2,15);
},
// 生成時(shí)間戳
createTimeStamp(){
return parseInt(new Date().getTime() / 1000) + ''
},
// 生成簽名
getSign(params, key){
let string = this.raw(params) + '&key=' + key;
let sign = createHash('md5').update(string).digest('hex');
return sign.toUpperCase();
},
// 生成系統(tǒng)的交易訂單號
getTradeId(type='wx'){
let date = new Date().getTime().toString();
let text = '';
let possible = '0123456789';
for(let i=0;i<5;i++){
text += possible.charAt(Math.floor(Math.random() * possible.length))
}
return (type == 'wx'?'ImoocWxJuZi':'ImoocMpJuZi') + date + text;
},
// Object 轉(zhuǎn)換成json并排序
raw(args){
let keys = Object.keys(args).sort();
let obj = {};
keys.forEach((key)=>{
obj[key] = args[key];
})
// {a:1,b:2} => &a=1&b=2
// 將對象轉(zhuǎn)換為&分割的參數(shù)
let val = '';
for(let k in obj){
val += '&' + k + '=' +obj[k];
}
return val.substr(1);
}
}復(fù)制代碼
下面是對支付的方法的封裝,其中調(diào)用了util中的函數(shù)??蛻舳苏{(diào)用的就是下面的 order 方法。
/**
* 微信小程序、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: function (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);
// 通過參數(shù)和簽名組裝xml數(shù)據(jù),用以調(diào)用統(tǒng)一下單接口
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
}, function (err, response, body) {
if (!err && response.statusCode == 200) {
xml.parseString(body.toString('utf-8'),(error,res)=>{
if(!error){
let data = res.xml;
console.log('data:' + JSON.stringify(data));
if (data.return_code[0] == 'SUCCESS' && data.result_code[0] == 'SUCCESS'){
// 獲取預(yù)支付的ID
let prepay_id = data.prepay_id || [];
let payResult = self.getPayParams(appid, prepay_id[0]);
resolve(payResult);
}
}
})
} else {
resolve(util.handleFail(err));
}
})
})
},
// 生成預(yù)支付的簽名
getPrePaySign: function (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();
},
// 簽名成功后 ,根據(jù)參數(shù)拼接組裝XML格式的數(shù)據(jù),調(diào)用下單接口
wxSendData: function (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;
},
getPayParams:function(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;
}
}復(fù)制代碼
最后定義 /pay/payWallet 的支付接口,里面調(diào)用公用的order方法。
// 小程序支付
router.get('/pay/payWallet',function(req,res){
let openId = req.query.openId;//用戶的openid
let appId = config.appId;//應(yīng)用的ID
let attach = "小程序支付課程體驗(yàn)";//附加數(shù)據(jù)
let body = "歡迎學(xué)習(xí)慕課首門支付專項(xiàng)課程";//支付主體內(nèi)容
let total_fee = req.query.money;//支付總金額
let notify_url = "http://localhost:3000/api/mp/pay/callback"
let ip = "123.57.2.144";
wxpay.order(appId,attach,body,openId,total_fee,notify_url,ip).then((result)=>{
res.json(util.handleSuc(result));
}).catch((result)=>{
res.json(util.handleFail(result.toString()))
});
})復(fù)制代碼
這里的流程請參見 官方描述 。官方描述的非常清楚,這兒就不描述更多了,其實(shí)主要就是拼接一些參數(shù),獲取 簽名 。然后根據(jù)簽名加上其他需要的 參數(shù) (參見上述代碼)再憑借xml的數(shù)據(jù)。然后再調(diào)用統(tǒng)一下單接口 https://api.mch.weixin.qq.com/pay/unifiedorder 。生成 prepay_id之后,生成小程序端需要的一些參數(shù),然后把這些參數(shù)返回個(gè)小程序客戶端,供小程序的客戶端調(diào)用微信小程序的支付功能。 小程序前端支付非常簡單,只是簡單的調(diào)用服務(wù)端提供的 payWallet 接口,傳入 openId 和 money 即可。然后獲取到相應(yīng)的參數(shù),調(diào)用微信提供的 requestPayment 拉起支付即可。 主要代碼邏輯如下:
pay() {
app.get(Api.payWallet,{
openId: Store.getItem('openId'),
money: this.data.index
}).then((res) => {
// 支付
wx.requestPayment({
timeStamp: res.timeStamp,
nonceStr: res.nonceStr,
package: res.package,
signType: res.signType,
paySign: res.paySign,
success: function (errmsg) {
if (errmsg == 'requestPayment:ok') {
wx.showToast({
title: '支付成功',
icon: 'success'
});
}
},
fail: function (res) {
if (res.errMsg == 'requestPayment:fail cancel') {
wx.showToast({
title: '支付取消',
icon: 'none'
});
} else {
wx.showToast({
title: res.errmsg,
icon: 'none'
});
}
}
})
});
}
復(fù)制代碼
到這里,小程序端的支付功能就已經(jīng)實(shí)現(xiàn)了。 |