请根据营业执照类型选择以下主体注册:
公众号认证后才可申请微信支付,认证费:300元/次 查看认证流程
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为48小时内。
资料审核通过后,开户信息会通过邮件、公众号发送给联系人,请按照指引填写财付通备付金汇入的随机金额,完成账户验证。(查看验证方法)
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。点此提前预览协议内容。
支付接口已获得,可根据开发文档进行开发,也可了解成功案例界面示意及素材。
业务流程说明:
$ npm i egg-init -g
$ egg-init zhufeng-wxpay --type=simple
$ cd zhufeng-wxpay
$ npm i
$ npm run dev
$ open localhost:7001
ssh -vnNT -R 7689:localhost:7001 root@47.105.88.180
yarn add egg-view-ejs
app/controller/home.js
async checkout() {
-
+ const { ctx, app } = this;
+ await ctx.render('checkout');
}
}
app/view/checkout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdn.bootcss.com/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdn.bootcss.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<title>微信支付</title>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-4">
<div class="card text-center mt-5">
<div class="card-header">
<h3>微信支付</h3>
</div>
<img src="/public/images/qrcode.png" class="card-img-top">
<div class="card-body">
<img src="/public/images/scan.png" alt="" class="w-100">
</div>
</div>
</div>
</div>
</div>
</body>
</html>
config/config.default.js
config.view={
defaultViewEngine: 'ejs',
defaultExtension:'.html',
mapping: {
'.html':'ejs'
}
}
config/plugin.js
exports.ejs = {
enable: true,
package: 'egg-view-ejs',
};
"dependencies": {
+ "axios": "^0.18.0",
+ "moment": "^2.22.2",
+ "qrcode": "^1.2.2",
+ "randomstring": "^1.1.5",
+ "uuid": "^3.3.2",
+ "xml-js": "^1.6.7"
}
app/router.js
router.post('/wxpay/notify',controller.home.notify);
app/controller/home.js
const Controller = require('egg').Controller;
const moment = require('moment');
const randomstring = require('randomstring');
const querystring = require('querystring');
const crypto = require('crypto');
const xmljs = require('xml-js');
const axios = require('axios');
const qrcode = require('qrcode');
class HomeController extends Controller {
async index() {
this.ctx.body = 'hi, egg';
}
wxSign(order, key,logger) {
//对参数进行排序
let sortedOrder = Object.keys(order).sort().reduce((memo, key) => {
memo[key] = order[key];
return memo;
}, {});
logger.info('排过序的订单', sortedOrder);
//转化成字符串
const stringifiedOrder = querystring.stringify(sortedOrder, null, null, {
encodeURIComponent: querystring.unescape
});
logger.info('不带key的订单字符串', stringifiedOrder);
//加上密钥
const stringifiedOrderWithKey = `${stringifiedOrder}&key=${key}`;
logger.info('带key的订单字符串', stringifiedOrderWithKey);
//计算出签名
const sign = crypto.createHash('md5').update(stringifiedOrderWithKey).digest('hex').toUpperCase();
return sign;
}
async checkout() {
const {
ctx,
app
} = this;
const {
logger
} = ctx;
const {
wxpay = {}
} = app.config;
//公众账号ID
const appid = wxpay.appid;
//商户号码 商户平台(https://pay.weixin.qq.com)->产品中心->开发配置->商户信息->商户号
//APPID授权管理 商户平台(https://pay.weixin.qq.com)->产品中心->开发配置->商户信息->商户号
const mch_id = wxpay.mch_id;
//回调通知url
const notify_url = wxpay.notify_url;
//密钥
const key = wxpay.key;
//统一下单接口
const unifiedorder = wxpay.unifiedorder;
//商户订单号
const out_trade_no = moment().local().format('YYYYMMDDhhmmss');
//商品描述
const body = '珠峰培训';
//商品价格
const total_fee = 1;
//支付类型
const trade_type = 'NATIVE';
//商品ID
const product_id = 100;
//随机字符串
const nonce_str = randomstring.generate(32);
//构建订单
let order = {
appid,
mch_id,
out_trade_no,
body,
total_fee,
product_id,
notify_url,
nonce_str,
trade_type
}
const sign = this.wxSign(order, key,logger);
logger.info('签名', sign);
//转换成XML格式
const xmlOrder = xmljs.js2xml({
xml: {
...order,
sign
}
}, {
compact: true
});
logger.info('XML订单', xmlOrder);
//请求统一下单接口
const unifiedorderResponse = await axios.post(unifiedorder, xmlOrder);
logger.info('统一下单响应', unifiedorderResponse.data);
const _prepay = xmljs.xml2js(unifiedorderResponse.data, {
compact: true,
cdataKey: 'value',
textKey:'value'
});
logger.info('_prepay', _prepay);
const prepay = Object.entries(_prepay.xml).reduce((memo, [key, value]) => {
memo[key]=value.value;
return memo;
}, {});
logger.info('prepay', prepay);
const {
code_url
} = prepay;
const qrcodeUrl = await qrcode.toDataURL(code_url, {
width: 300
});
await ctx.render('checkout', {
qrcodeUrl
});
}
parseXML(req) {
return new Promise(function(resolve,reject){
let buffers = [];
req.on('data', function(data) {
buffers.push(data);
});
req.on('end', function() {
let ret = Buffer.concat(buffers);
resolve(ret.toString());
});
});
}
async notify() {
const {ctx,app} = this;
const { logger } = ctx;
const {
wxpay = {}
} = app.config;
const key = wxpay.key;
let body = await this.parseXML(ctx.req);
logger.debug('request',body);
const _payment = xmljs.xml2js(body, {
compact: true,
cdataKey: 'value',
textKey:'value'
});
logger.info('_payment', _payment);
const payment = Object.entries(_payment.xml).reduce((memo, [key, value]) => {
memo[key] = value.value;
return memo;
}, {});
logger.info('payment', payment);
const paymentSign = payment.sign;
logger.debug('paymentSign', paymentSign);
delete payment.sign;
const mySign = this.wxSign(payment, key,logger);
logger.debug('mySign', mySign);
const return_code = paymentSign === mySign ? 'SUCCESS' : "FAIL";
const reply = {
xml: {
return_code
}
}
const ret = xmljs.js2xml(reply, {
compact: true
});
logger.debug('通知返回', ret);
ctx.body = ret;
}
}
module.exports = HomeController;
app/view/checkout.html
- <img src="/public/images/qrcode.png" class="card-img-top">
+ <img src="<%=qrcodeUrl%>" class="card-img-top">
config/config.default.js
config.logger={
level: 'DEBUG',
allowDebugAtProd: true,
consoleLevel: 'DEBUG'
}
exports.security = {
csrf: {
enable: false,
ignoreJSON: true
}
};
config.wxpay={
appid: '',
mch_id: '',
key: '',
notify_url: 'http://front.zhufengpeixun.cn/wxpay/notify',
unifiedorder:'https://api.mch.weixin.qq.com/pay/unifiedorder'
}
router.get('/wxpay/query',controller.home.query);
app/view/result.html
<body>
<div class="container">
<div class="jumbotron mt-5 text-center">
<h1><%=trade_state_desc%></h1>
</div>
</div>
</body>
config/config.default.js
+ unifiedorder: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
+ orderquery:'https://api.mch.weixin.qq.com/pay/orderquery'
home.js
'use strict';
const Controller = require('egg').Controller;
const moment = require('moment');
const randomstring = require('randomstring');
const querystring = require('querystring');
const crypto = require('crypto');
const transformer = require('xml-js');
const axios = require('axios');
const qrcode = require('qrcode');
class HomeController extends Controller {
async index() {
this.ctx.body = 'hi, egg';
}
wxSign(order, key,logger) {
//对参数进行排序
let sortedOrder = Object.keys(order).sort().reduce((memo, key) => {
memo[key] = order[key];
return memo;
}, {});
logger.info('排过序的订单', sortedOrder);
//转化成字符串
const stringifiedOrder = querystring.stringify(sortedOrder, null, null, {
encodeURIComponent: querystring.unescape
});
logger.info('不带key的订单字符串', stringifiedOrder);
//加上密钥
const stringifiedOrderWithKey = `${stringifiedOrder}&key=${key}`;
logger.info('带key的订单字符串', stringifiedOrderWithKey);
//计算出签名
const sign = crypto.createHash('md5').update(stringifiedOrderWithKey).digest('hex').toUpperCase();
return sign;
}
xml2js(data,logger) {
const _result = transformer.xml2js(data, {
compact: true,
cdataKey: 'value',
textKey:'value'
});
logger.info('_result', _result);
const result = Object.entries(_result.xml).reduce((memo, [key, value]) => {
memo[key]=value.value;
return memo;
},{});
logger.info('result', result);
return result;
}
async checkout() {
const {
ctx,
app
} = this;
const {
logger
} = ctx;
const {
wxpay = {}
} = app.config;
//公众账号ID
const appid = wxpay.appid;
//商户号码 商户平台(https://pay.weixin.qq.com)->产品中心->开发配置->商户信息->商户号
//APPID授权管理 商户平台(https://pay.weixin.qq.com)->产品中心->开发配置->商户信息->商户号
const mch_id = wxpay.mch_id;
//回调通知url
const notify_url = wxpay.notify_url;
//密钥
const key = wxpay.key;
//统一下单接口
const unifiedorder = wxpay.unifiedorder;
//商户订单号
const out_trade_no=moment().local().format('YYYYMMDDhhmmss');
ctx.session.out_trade_no=out_trade_no;
//商品描述
const body = '珠峰培训';
//商品价格
const total_fee = 1;
//支付类型
const trade_type = 'NATIVE';
//商品ID
const product_id = 100;
//随机字符串
const nonce_str = randomstring.generate(32);
//构建订单
let order = {
appid,
mch_id,
out_trade_no,
body,
total_fee,
product_id,
notify_url,
nonce_str,
trade_type
}
const sign = this.wxSign(order, key,logger);
logger.info('签名', sign);
//转换成XML格式
const xmlOrder = transformer.js2xml({
xml: {
...order,
sign
}
}, {
compact: true
});
logger.info('XML订单', xmlOrder);
//请求统一下单接口
const unifiedorderResponse = await axios.post(unifiedorder, xmlOrder);
logger.info('统一下单响应', unifiedorderResponse.data);
const prepay=this.xml2js(unifiedorderResponse.data,logger);
logger.info('prepay', prepay);
const {
code_url
} = prepay;
const qrcodeUrl = await qrcode.toDataURL(code_url, {
width: 300
});
await ctx.render('checkout', {
qrcodeUrl
});
}
parseXML(req) {
return new Promise(function(resolve,reject){
let buffers = [];
req.on('data', function(data) {
buffers.push(data);
});
req.on('end', function() {
let ret = Buffer.concat(buffers);
resolve(ret.toString());
});
});
}
async notify() {
const {ctx,app} = this;
const { logger } = ctx;
const {
wxpay = {}
} = app.config;
const key = wxpay.key;
let body = await this.parseXML(ctx.req);
logger.debug('request',body);
const payment=this.xml2js(body,logger);
logger.info('payment', payment);
const paymentSign = payment.sign;
logger.debug('paymentSign', paymentSign);
delete payment.sign;
const mySign = this.wxSign(payment, key,logger);
logger.debug('mySign', mySign);
const return_code = paymentSign === mySign ? 'SUCCESS' : "FAIL";
const reply = {
xml: {
return_code
}
}
const ret = transformer.js2xml(reply, {
compact: true
});
logger.debug('通知返回', ret);
ctx.body = ret;
}
async query() {
const {
ctx,
app
} = this;
const {
logger
} = ctx;
const {
wxpay = {}
} = app.config;
//公众账号ID
const appid = wxpay.appid;
//商户号码
const mch_id = wxpay.mch_id;
//密钥
const key = wxpay.key;
//统一下单接口
const orderquery=wxpay.orderquery;
//随机字符串
const nonce_str = randomstring.generate(32);
let query={
appid,
mch_id,
out_trade_no: ctx.session.out_trade_no,
nonce_str
}
let sign=this.wxSign(query,key,logger);
logger.info('签名', sign);
//转换成XML格式
const xmlQuery = transformer.js2xml({
xml: {
...query,
sign
}
}, {
compact: true
});
logger.info('XML查询条件', xmlQuery);
//请求统一下单接口
const orderqueryResponse = await axios.post(orderquery, xmlQuery);
logger.info('订单查询响应',orderqueryResponse.data);
const queryResult =this.xml2js(orderqueryResponse.data,logger);
logger.info('queryResult',queryResult);
await ctx.render('result',queryResult);
}
}
module.exports = HomeController;