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:
-
The user initiates a payment request at the front end (passing business parameters to the server: money, commodity information,…)
-
Create some parameters for wechat to initiate an order request (timestamp, random string, merchant order number, signature…)
-
The server organizes the request parameters in XML format and initiates a unified single request to wechat to obtain the prepay_id
-
Return parameters required by the applets’ payment API (timeStamp, nonceStr, signType, Package, paySign)
-
After obtaining the required parameters for payment, the applet side calls the Wx.requestPayment () API and invokes the payment popup
-
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 😀