preface

There are several ways of wechat payment: payment code payment, Native payment, APP payment, H5 payment, JSAPI payment, small program payment. JSAPI payment refers to the payment initiated by users opening H5 webpage in wechat APP (wechat browser). Applets payment also belongs to JSAPI payment.

General process of wechat mini program payment:

  1. The user initiates a payment request at the front end (passing business parameters to the server: money, commodity information,…)

  2. Create some parameters for wechat to initiate an order request (timestamp, random string, merchant order number, signature…)

  3. The server organizes the request parameters in XML format and initiates a unified single request to wechat to obtain the prepay_id

  4. Return parameters required by the applets’ payment API (timeStamp, nonceStr, signType, Package, paySign)

  5. After obtaining the required parameters for payment, the applet side calls the Wx.requestPayment () API and invokes the payment popup

  6. The server handles notify_URL callback notifications of payment results

Wechat Pay V2

Defining the config file

Start by defining the relevant configuration in the configuration file

// config/index.js

module.exports = {
  / / baseUrl: 'http://127.0.0.1:3000',
  baseUrl: 'http://192.168.5.96:3000'.// Local routing IP domain name + port
  // Wechat applets
  mp: {
   appId: 'XXXXXX'.// appid
   appSecret: 'XXXXXX' // app secret
  },
  // Merchant number information
  mch: {
    mchId: 'XXXXXX'./ / merchant id
    mchKey: 'XXXXXX' // api key}},Copy the code

Create the timestamp in seconds

// utils/mpPayUtil.js

const request = require('request');
const { mp: mpConfig, mch: mchConfig } = require('.. /config');
const crypto = require('crypto');
const xml2js = require('xml2js'); // XML parsing module

/** * Create timestamp (s) *@return {String} '1628515132' * /
const _creatTimeStamp = () = > {
  return parseInt(+new Date(a) /1000) + ' ';
};
Copy the code

Creating random strings

// utils/mpPayUtil.js

/** * generates a random string *@param {Number} StrLen The string length is *@return {String} 'hKcmnxAEVFsJVLwQgx1s'
 */ 
const _createNonceStr = (strLen = 20) = > {
  const str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let nonceStr = ' ';
  for (let i = 0; i < strLen; i++) {
    nonceStr += str[Math.floor(Math.random() * str.length)];
  }

  return nonceStr;
};
Copy the code

Create order Number

// utils/mpPayUtil.js

/** * create order number *@return {String} '1628515375405237420' * /
const _createTradeNo = () = > {
  let tradeNo = ' ';
  const timeStampStr = (+new Date() + ' '); // Timestamp string
  let randomNumstr = ' ';

  const numStr = '0123456789';
  for (let i = 0; i < 6; i++) {
    randomNumstr += numStr[Math.floor(Math.random() * numStr.length)];
  }
  
  tradeNo = timeStampStr + randomNumstr;
  return tradeNo;
};
Copy the code

To generate the signature

Signature sign: indicates all non-empty parameters to be passed. The parameters are sorted in alphabetical order based on the ASCII characters of the parameter names. The format of URL key-value pairs is used (key1=value1&key2=value2…). Concatenated to the string stringA. The stringA string is concatenated with the key to obtain the stringSignTemp string, which is then converted to uppercase by an MD5 operation.

Parameters: can be selected according to their own needs, but the interface document requires mandatory, it must be passed, otherwise the interface will fail to call.

// utils/mpPayUtil.js

/** * The signature algorithm uses MD5 *@param {Object} ParamObj Specifies the parameter object */ to be signed 
const _createSign = signParamObj= > {
  // Sort the key of the object (sort all parameters to be signed in alphabetical order according to the ASCII key of the field name)
  const signParamKeys = Object.keys(signParamObj).sort();
  const stringA = signParamKeys.map(signParamKey= > `${ signParamKey }=${ signParamObj[signParamKey] }`).join('&'); // a=1&b=2&c=3
  const stringSignTemp = stringA + `&key=${ mchConfig.mchKey }`; // Note: key Indicates the key key set for the merchant platform
  / / signature
  const _sign = crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();

  return _sign;
};
Copy the code

Convert the OBj parameter to the XML format required for the order

// utils/mpPayUtil.js

/** * Convert obj to wechat submit XML format, including signature *@param {Object} ParamObj Converts the object *@return {String<XML>}* /
const _createXMLData = paramObj= > {
  let formData = ' ';
  formData += '<xml>';

  Object.keys(paramObj).sort().map(itmKey= > {
    formData += ` <${ itmKey }>${ paramObj[itmKey] }</${ itmKey }> `;
  });

  formData += `</xml>`;
  return formData;
};
Copy the code

Create wechat pre-payment order ID

Single interface Mandatory parameters: Appid, McH_id, nonce_str, sign_type, body, out_trade_no, total_fee, SPbill_create_IP, notifY_URL, trade_type, sign, Where sign is the encrypted character of the previously passed parameter; Convert the object parameters to XML format and initiate a pre-paid order request. The returned prepayment ID (prepay_ID) is returned only when both return_code and result_code are SUCCESS in the result returned by wechat in XML format.

// utils/mpPayUtil.js

/ * * * create WeChat prepaid order id * https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1 *@param {String<XML>} xmlFormData xml
 * @return {Object} { prepay_id } Prepayment ID */
const _v2createPrePayOrder = async xmlFormData => {
  // Request the unified ordering interface of wechat server
  const requrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

  return new Promise((resolve, reject) = > {
    request({ url: requrl, method: 'POST'.body: xmlFormData }, (error, response, body) = > {
      // console.log(body); Wechat returns data in XML format
      if(error || response.statusCode ! = =200) return reject({ errmsg: 'Failed to request wechat server' });

      // Parse data in XML format
      xml2js.parseString(body, (err, result) = > {
        if (err) return reject({ errmsg: 'XML parsing error' }); // Parsing error
        const resData = result.xml;
        // console.log(' XML parsing result :', resData);
        // the prepayment ID (prepay_id) is returned only when both return_code and result_code are SUCCESS.
        if (resData.return_code[0= = ='SUCCESS' && resData.result_code[0= = ='SUCCESS' && resData.prepay_id) {
          resolve({ prepay_id: resData.prepay_id[0]}); }else{ reject(resData); }}); }); }); };Copy the code

Get payment parameters (entry)

Export the payment callback function that returns the parameter object required by wx.requestPayment() on the applet side.

// utils/mpPayUtil.js

/** * Get payment parameters *@param {Object} The param object *@return {Object} Parameters required for front-end payment */
exports.v2getPayParam = async param => {
  const { openid, attach, body, total_fee, notify_url, spbill_create_ip } = param;
  const appid = mpConfig.appId; // wechat small program appid
  const mch_id = mchConfig.mchId; / / merchant id
  const timeStamp = _creatTimeStamp(); // Timestamp string (seconds)
  const nonce_str = _createNonceStr(); // Random string
  const out_trade_no = _createTradeNo(); / / order number
  const sign_type = 'MD5'; // Signature type
  const trade_type = 'JSAPI'; // Transaction type (applet payment method)
  / / signature
  const sign = _createSign({ appid, mch_id, nonce_str, out_trade_no, sign_type, trade_type, openid, attach, body, total_fee, notify_url, spbill_create_ip });
  // Data in XML format
  const xmlFormData = _createXMLData({ appid, mch_id, nonce_str, out_trade_no, sign_type, trade_type, openid, attach, body, total_fee, notify_url, spbill_create_ip, sign });

  try {
    // Create a wechat pre-payment ID
    const { prepay_id } = await _v2createPrePayOrder(xmlFormData);
    if(! prepay_id)return ' ';
    
    const payParamObj = {
      appId: appid, // The appID must be added, otherwise an error is reported: payment verification signature failed
      timeStamp,
      nonceStr: nonce_str,
      signType: 'MD5'.package: `prepay_id=${ prepay_id }`
    };
    // Pay the signature
    const paySign = _createSign(payParamObj);

    return {
      timeStamp: payParamObj.timeStamp,
      nonceStr: payParamObj.nonceStr,
      signType: payParamObj.signType,
      package: `prepay_id=${ prepay_id }`,
      paySign
    };
  } catch (error) {
    console.log('Error creating payment order', error);
    return ' '; }};Copy the code

Routing: Request payment parameters

Pay the order route, where total_fee is the total amount of the order, the unit is [minutes], and the parameter value cannot be decimal. In other words, 1 = 1 point, 10 = 1 jiao, 100 = 1 yuan, 1000 = 10 yuan…

// route/mpRoute.js

const express = require('express');
const router = express.Router();

const { mp: mpConfig, baseUrl } = require('.. /config');
const commonUtil = require('.. /utils');
const mpPayUtil = require('.. /utils/mpPayUtil');

/** * obtain the parameters required by the small program side payment */
router.get('/v2Pay'.async (req, res) => {
  const openid = req.query.userOpenid; // The user's openID
  const total_fee = Number(req.query.money) * 100; // The payment amount is in minutes
  const attach = 'Payment additional Data'; // Attach data
  const body = 'Applet payment';  // Body content
  const notify_url = `${ baseUrl }/api/mp/payCallback`; // Callback address for receiving the notification of wechat payment result asynchronously. The notification URL must be accessible to the external network without parameters. The public domain name must be HTTPS
  const spbill_create_ip = '192.168.5.96'; // Terminal IP address (local routing IP)
  
  const param = { openid, attach, body, total_fee, notify_url, spbill_create_ip };
  const payParam = await mpPayUtil.v2getPayParam(param);
  if(! payParam)return res.send(commonUtil.resFail('Error creating payment order'));

  res.send(commonUtil.resSuccess(payParam));
});
Copy the code

Routing: Pay for notification of callback results

The order interface will bring the notify_URL parameter and receive the notification address of wechat payment results. The address must be the IP address of the interface that can be accessed from the Internet. It must be HTTPS, and the request is A POST request. This interface will be called by the wechat server and return the corresponding data regardless of whether the payment is successful or not. Therefore, relevant business logic can be written in this interface, such as writing into the database after the payment is successful, and a reply can be returned to the wechat server to inform that the notification has been received.

Since the parameters returned by the callback are also in XML format, they cannot be received using the conventional methods. Therefore, you need to install a parsing middleware express-XML-bodyParser, and you can use req.body. XML to get parsed parameters.

// app.js

const xmlparser = require('express-xml-bodyparser'); // Parse the wechat Pay callback notification XML
app.use(xmlparser());

// route/mpRoute.js

/** * Pay result notification (need to ensure that the applet is online before callback) need to return the result format for POST ** * write related business logic in this interface, such as after successful payment write to the database, etc. * https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8 */
router.post('/payCallback'.async (req, res) => {
  console.log(req.body.xml);
  / / json to XML
  const _json2Xml = json= > {
    let _xml = ' ';
    Object.keys(json).map((key) = > {
        _xml += ` <${ key }>${ json[key ]}</${ key }> `
    });
    return `<xml>${ _xml }</xml>`;
  }
  const sendData = { return_code: 'SUCCESS'.return_msg: 'OK' };
  res.end(_json2Xml(sendData));
});
Copy the code

Others: Tool function encapsulation

// utils/index.js

/** * Encapsulation success response */
exports.resSuccess = (data = null) = > {
  return { code: 0, data, message: 'success' };
};

/** * Encapsulation failed response */
exports.resFail = (message = 'Server error', code = 10001) = > {
  return { message, code, data: null}; }; .Copy the code

The small program end

The applet first initiates a payment parameter request to the server, and then passes it to Wx. requestPayment, which invokes the payment popup.

/** * wechat pay v2 */
async _v2Pay(param) {
  const { userOpenid, money } = param;
   try {
    // Initiate an HTTP request to get payment parameters
    const { data: payParam } = await app.$fetchReq(app.$api.v2Pay, { userOpenid, money });
    // Call up the payment API
    wx.requestPayment({
      timeStamp: payParam.timeStamp,
      nonceStr: payParam.nonceStr,
      package: payParam.package,
      paySign: payParam.paySign,
      signType: payParam.signType,
      success: res= > {
        console.log(res);
        if (res.errMsg == 'requestPayment:ok') wx.showToast({ title: 'Payment successful'.icon: 'success' });
      },
      fail: error= > {
        console.log(error);
        if (error.errMsg == 'requestPayment:fail cancel') wx.showToast({ title: 'Payment cancelled'.icon: 'none'}); }}); }catch (error) {
    console.log(error); }}Copy the code

Wechat cloud payment

Compared to the self-service server, using cloud development to implement the corresponding payment function, no need to care about certificates, signatures,… The payment field in the return JSON format is the information required to call wx.requestPayment() on the small program side.

Cloud function

const cloud = require('wx-server-sdk');
cloud.init();

const envId = 'XXXXXX';
const subMchId = 'XXXXXX';
const functionName = 'pay-callback';

/** ** ** */
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext();
  const openid = wxContext.OPENID;
  const { money } = event;

  const attach = 'Payment additional Data';
  const body = 'Applet payment';
  const nonceStr = _createNonceStr();
  const outTradeNo = _createOutTradeNo();
  const totalFee = Number(money) * 100;

  const res = await cloud.cloudPay.unifiedOrder({
    openid, / / users openid
    subMchId, // Submerchant number
    totalFee, // The payment amount is in minutes
    envId, // The result informs the callback to the cloud function environment
    functionName, // The result informs the callback cloud function name
    outTradeNo, // Create order number
    attach, // Attach data
    body, // Body content
    nonceStr, // Random string
    tradeType: 'JSAPI'.// Transaction type
    spbillCreateIp : '127.0.0.1'./ / terminal IP
  });

  const { returnCode, payment } = res;
  if(returnCode ! = ='SUCCESS') return { message: 'Request for payment order failed' };
  
  return { payment };
};

/** * creates a random string */
 const _createNonceStr = (strLen = 20) = > {
  const str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let nonceStr = ' ';
  for (let i = 0; i < strLen; i++) {
    nonceStr += str[Math.floor(Math.random() * str.length)];
  }

  return nonceStr;
};

/** ** Create order number */
const _createOutTradeNo = () = > {
  const date = new Date(a);// The current time
  / / year
  const Year = `${ date.getFullYear() }`;
  / / month
  const Month = `${ date.getMonth() + 1 < 10 ? ` 0${ date.getMonth() + 1 }` : date.getMonth() + 1 }`;
  / /,
  const Day = `${ date.getDate() < 10 ? ` 0${ date.getDate() }` : date.getDate() }`;
  / /
  const hour = `${ date.getHours() < 10 ? ` 0${date.getHours()}` : date.getHours() }`;
  / / points
  const min = `${ date.getMinutes() < 10 ? ` 0${date.getMinutes()}` : date.getMinutes() }`;
  / / SEC.
  const sec = `${ date.getSeconds() < 10 ? ` 0${ date.getSeconds() }` : date.getSeconds() }`;
  / / time
  const formateDate = `${ Year }${ Month }${ Day }${ hour }${ min }${ sec }`;
  // console.log(' time :', formateDate);

  return `The ${Math.round(Math.random() * 1000)}${ formateDate + Math.round(Math.random() * 89 + 100).toString()}`;
};

Copy the code

The small program end

/** * Cloud payment */
async _cloudPay({ money }) {
  try {
    wx.showLoading({ title: 'Loading... '.mask: true });
    const result = (await wx.cloud.callFunction({
        name: 'pay-req'.data: { money }
      })).result;
      const { payment } = result;
      // Call up the payment APIwx.requestPayment({ ... payment,// Call the payment API to initiate a payment based on the obtained parameters
        success: res= > {
          console.log(res);
          if (res.errMsg == 'requestPayment:ok') wx.showToast({ title: 'Payment successful'.icon: 'success' });
        },
        fail: error= > {
          console.log(error);
          if (error.errMsg == 'requestPayment:fail cancel') wx.showToast({ title: 'Payment cancelled'.icon: 'none'}); }}); }catch (error) {
      wx.hideLoading();
      console.log(error); }}Copy the code

The last

Code address: pay-server

If there are any mistakes in this article or you have your own opinion, please leave a comment at 😀