Мини-программа, авторизация входа в систему H5, обмен, процесс оплаты

JavaScript

Вход в WeChat, совместное использование, процесс оплаты

[TOC]

предисловие

Для внешнего интерфейса WeChat支付,分享,登录Это должно быть освоено.Сегодняшняя статья в основном подробно знакомит с процессом этих трех аспектов. Основное содержание следующее:

Введение в знания, связанные с доменными именами

  1. Имя бизнес-домена: щелкните текстовое поле в браузере WeChat, появится всплывающее окно.该网站不安全,请不要输入密码, эту проблему можно решить, настроив имя бизнес-домена.
  2. Имя домена безопасности интерфейса JS: вам нужно попробовать это доменное имя при совместном использовании функции (js-sdk).
  3. Авторизованное доменное имя веб-страницы: используется для получения уникального идентификатора пользователя openid для официальной учетной записи.

Процесс авторизации апплета WeChat

op=>operation: openid判断是否登录授权
op2=>operation: 根据wx.login获取code
op3=>operation: 调用服务端根据code换取openid
op4=>operation: 通过用户授权,获取信息,存到数据库
op->op2->op3->op4

Если вы никогда не читали документ авторизации входа в мини-программу, я предлагаю вам взглянуть на следующий адрес:

Официальная документация сервераКлиентская документация

nodejs + апплет реализует авторизованный вход в систему

внешний интерфейс

  1. Определите, нужно ли входить в систему, в зависимости от того, есть ли локально userId, если нет, получите openid пользователя.
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)
         })
       }
     }
   })
 }
  1. Нажмите кнопку «Авторизовать», чтобы инициировать запрос на вход.
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
        })
    })
}

серверная часть

существуетconfigВнутри определите общественностьappidа такжеappsecret

module.exports = {
    wx: {
        appId: 'wx0ef10432747d8f57',
        appsecret: 'cc47a6127687e999a1dffa756ff83c0e'
    },
    mp: {
        appId: 'wx0691f1dcf6e5e231',
        appSecret: 'c7ed875e338120f15f49476a6596eb4f'
    }
}

Затем, вызвав апплетофициальная документацияинтерфейс, получить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)
    })
})

登录接口пишу

// 小程序授权登录
router.get('/login',async function(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.handleSuc({
          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.handleSuc({
            userId: result.data[0]._id
          }))
        }else{
          res.json(insertData);
        }
      }
    }else{
      res.json(userRes);
    }
  }
})

приведенного выше кодаhandleFailа такжеhandleResponseЭто инкапсулированная унифицированная обработка данных.Если вам интересно, см.githubадрес. Код здесь не показан.

Следует отметить, что в этой реализацииopenidПоведение реализовано на бэкенде. Это может быть реализовано на переднем конце, но это будет относительно хлопотно. На данный момент suerId уже хранится в базе данных и сохраняется локально, при следующем входе в систему, если есть userId, вам не нужно снова входить в систему.

Авторизация входа в систему H5 и процесс обмена

Авторизация входа для H5 немного отличается. Если пользователь входит на страницу авторизации и обнаруживает, что у него нет авторизации для входа, необходимо перейти на страницу авторизации.официальная документацияДанный процесс выглядит следующим образом:

1 Шаг 1: Пользователь соглашается на авторизацию и получает код2 Шаг 2: Обмен кодом для авторизации access_token3 Шаг 3: Обновите access_token (при необходимости) 4 Шаг 4: Получите информацию о пользователе (область действия должна быть snsapi_userinfo) 5 Приложение: проверьте, действительны ли учетные данные авторизации (access_token).

Код в проекте выглядит следующим образом: (Этот код не реализует обновление access_token и извлечение информации о пользователе)

При загрузке страницы определяется, авторизована ли она.

mounted(){
   this.checkUserAuth();
 },
methods:{
// 检查用户是否授权过
checkUserAuth(){
  let openId = this.$cookie.get('openId');
  if(!openId){
    // 如果没有登录授权,则跳转到微信提供的跳转页面。
    window.location.href = API.wechatRedirect;
  }else{
  // 如果用户已经授权,则调用获取微信配置信息接口
    this.getWechatConfig();
  }
},

API.wechatRedirect:

wechatRedirect:'/api/wechat/redirect?url=http%3A%2F%2Fm.51purse.com%2F%23%2Findex&scope=snsapi_userinfo',

"Уведомление"

  • URL-адрес требуетсяencodeURIComponentПросто кодирование.
  • m.51purse.comЕго необходимо настроить вместе с вами в фоновом режиме общедоступной учетной записи WeChat.授权域名Последовательный!

Реализация интерфейса обратного вызова авторизации при входе в систему с помощью nodejs в основном предназначена для получения параметров запроса клиента и запроса параметров, предоставленных WeChat.接口

// 用户授权重定向
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);
  // 获取到客户端带过来的数据,请求微信接口
  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);
})

когда пользователь нажимает确认授权После этого прыжок будет выполненcallbacl:http://m.51purse.com/api/wechat/getOpenId. И этот интерфейс также реализован на стороне ноды, детали таковы:

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


// 根据code获取用户的OpenId
router.get('/getOpenId',async function(req,res){
  let code = req.query.code;
  console.log("code:"+code);
  if(!code){
    res.json(util.handleFail('当前未获取到授权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){
          // 从数据库查找到用户信息后,回调到客户端的页面
          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);
    }
  }
})

"Примечание": в приведенном выше коде для простоты удален некоторый ненужный код. Если вам интересно, посетите gitHub.

Процесс обмена H5

Точно так же, если вы не читали разработку WeChat H5официальная документация, я предлагаю вам прочитать это в первую очередь. Что касается совместного использования, вы должны прочитать следующее:

При повторном вызове страницы openId был получен из файла cookie. Клиент продолжит выполнять приведенный ниже код. Получите информацию о конфигурации, возвращенную сервером, для инициализации функции совместного использования.

Перед этим нужноnpm install wx-jssdk

// 这个信息统一定义在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, // 开启调试模式,调用的所有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);
      })
    }
  })
}

Общая функция инкапсулирована в util/index.js.

export default {
  //获取浏览器地址栏参数值
  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', // 分享标题
      desc: 'xxxx', // 分享描述
      link: 'http://m.51purse.com/#/index', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
      imgUrl: '', // 分享图标
    }
    wx.onMenuShareAppMessage(shareInfo);
    wx.onMenuShareTimeline(shareInfo);
    wx.onMenuShareQQ(shareInfo);
    wx.onMenuShareQZone(shareInfo);
    // 下面两种方法为新的方法,上面的方法将会被淘汰。
    wx.updateAppMessageShareData(shareInfo);
    wx.updateTimelineShareData(shareInfo);
  }
}

конечная пара nodejs/wechat/jssdkРеализация интерфейса следующая:

// common.getToken()方法 获取基础接口的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 = {
      // 生成随机字符串
      noncestr:util.createNonceStr(),
      // 生成时间戳
      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, // 必填,公众号的唯一标识
        timestamp: params.timestamp, // 必填,生成签名的时间戳
        nonceStr: params.noncestr, // 必填,生成签名的随机串
        signature: sign,// 必填,签名
        jsApiList: [
          'updateAppMessageShareData',
          'updateTimelineShareData',
          'onMenuShareTimeline',
          'onMenuShareAppMessage',
          'onMenuShareQQ',
          'onMenuShareQZone',
          'chooseWXPay'
        ] // 必填,需要使用的JS接口列表
      }))
    }
  }else{
    res.json(result);
  }
})

Приведенный выше код в основном получает базовыйtoken, то используйте базуtokenВ сочетании с соответствующими параметрами, такими как подпись, отметка времени, случайное число и т. д., соответствующие параметры возвращаются клиенту.

должны знать о том,基础tokenа такжеaccessTokenразница. предложениеСправочная статья.

На данный момент доступ WeChat H5 к jssdk для совместного использования завершен.

Оплата мини-программы

Платежный интерфейс небольшой программы

  • получить openId
  • вызвать цифровую подпись

Внутренний процесс оплаты

  • Общие параметры сшивания
  • Создать подпись
  • объединение xml-данных
  • Вызов интерфейса заказа
  • Получить идентификатор предоплаты: prepay_id
  • Создать платежный SDK
  • Определите интерфейс обратного вызова для приема платежных сообщений WeChat.

Основная логика оплаты на стороне сервера

Процесс на стороне сервера описан ниже в коде. Во-первых, некоторые общедоступные методы, необходимые для оплаты, инкапсулированы в util.

/**
 * 公共函数定义
 */
let createHash = require('create-hash');
module.exports = {
  // 生成随机数
  createNonceStr(){
    return Math.random().toString(36).substr(2,15);
  },
  // 生成时间戳
  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();
  },
  // 生成系统的交易订单号
  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 转换成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
    // 将对象转换为&分割的参数
    let val = '';
    for(let k in obj){
      val += '&' + k + '=' +obj[k];
    }
    return val.substr(1);
  }
}

Ниже приведена инкапсуляция метода оплаты, который вызывает функцию в util. Клиент вызывает следующее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);
      // 通过参数和签名组装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
      }, 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'){
                // 获取预支付的ID
                let prepay_id = data.prepay_id || [];
                let payResult = self.getPayParams(appid, prepay_id[0]);
                resolve(payResult);
              }
            }
          })
        } else {
          resolve(util.handleFail(err));
        }
      })
    })
  },
  // 生成预支付的签名
  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();
  },
  // 签名成功后 ,根据参数拼接组装XML格式的数据,调用下单接口
  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;
  }
}

окончательное определение/pay/payWalletПлатежный интерфейс, вызывающий метод публичного заказа.

// 小程序支付
router.get('/pay/payWallet',function(req,res){
  let openId = req.query.openId;//用户的openid
  let appId = config.appId;//应用的ID
  let attach = "小程序支付课程体验";//附加数据
  let body = "欢迎学习慕课首门支付专项课程";//支付主体内容
  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()))
  });
})

Смотрите процесс здесьОфициальное описание. Официальное описание очень понятное, поэтому я не буду здесь больше описывать, на самом деле, главное склеить некоторые параметры, чтобы получить签名. Затем добавьте другие необходимые в соответствии с подписью参数(См. приведенный выше код), а затем полагайтесь на данные xml. Затем вызовите единый интерфейс заказаhttps://api.mch.weixin.qq.com/pay/unifiedorder. генерироватьprepay_idПосле этого сгенерируйте некоторые параметры, требуемые апплетом, а затем верните эти параметры клиенту апплета, чтобы клиент апплета вызвал платежную функцию апплета WeChat.

Внешний платеж апплета очень прост, просто позвоните в службу, предоставляемую сервером.payWalletинтерфейс, входящийopenIdа такжеmoneyВот и все. Затем получите соответствующие параметры, позвоните в предоставленный WeChatrequestPaymentПоднимитесь, чтобы заплатить.

Основная логика кода выглядит следующим образом:


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

На данный момент реализована платежная функция апплета.