Node.js 整合 Express 框架实现微信支付和支付宝支付
Posted 魏晓蕾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js 整合 Express 框架实现微信支付和支付宝支付相关的知识,希望对你有一定的参考价值。
npm 安装依赖包
npm init -y # 初始化package.json文件
npm install express # 安装express框架
npm install axios # 安装axios远程请求工具包
npm install qs # 安装用于字符串转换的工具包qs
npm install mongoose # 安装mongodb工具包
npm install xml2js # 安装用于转换微信支付返回的xml格式数据为json格式的工具包xml2js
npm install moment # 安装用于计算access_token和jsapi_ticket的过期时间的工具包moment
npm install fs # 安装用于读取本地文件的工具包fs
npm install cookie-parser # 安装cookie解析工具包cookie-parser
npm install alipay-sdk # 安装支付宝SDK alipay-sdk
npm install crypto # 安装微信支付签名工具包crypto,使用MD5算法签名
npm install sha1 # 安装微信支付JSAPI签名工具包sha1,使用SHA1算法签名
npm install url # 安装用于获取支付路径URL拼接的工具包url
npm install ejs # 安装ejs模板,生成前端页面
服务器环境配置
- 复制本地公钥到远程服务器
adduser web # 远程主机添加用户
sudo vim /etc/sudoers # 打开sudoers文件
web ALL=(ALL:ALL) ALL # 追加配置内容
# 远程服务器配置ssh登录
ssh-copy-id -i ~/ssh/id_rsa.pub web@ip # 复制本地公钥到远程主机
ssh web@ip # 远程登录
- 服务器软件安装
sudo apt-get update # 更新资源
sudo apt-get install mongodb -y # 安装mongodb数据库
sudo apt-get install nginx -y # 安装nginx服务器
sudo apt-get install git -y # 安装git
sudo apt-get install nodejs -y # 安装nodejs
sudo apt-get install npm -y # 安装npm
sudo npm i n -g # 安装nodejs的模块n,用来安装最新版本的nodejs
sudo n latest # 安装最新版本的node
sudo npm i nrm -g # 安装nrm模块,用来切换npm安装代码时的源
nrm use taobao # 使用taobao镜像
sudo npm i pm2 -g # 安装pm2模块
- 生成 ssh 密钥
ssh-key-gen # 在服务器上生成一个密钥,用于拉取仓库代码
- nginx 配置
upstream my_nodejs_upstream {
server 127.0.0.1:3001;
keepalive 64;
}
server {
listen 443 ssl;
server_name www.my-website.com;
ssl_certificate_key /etc/ssl/main.key;
ssl_certificate /etc/ssl/main.crt;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://my_nodejs_upstream/;
proxy_redirect off;
proxy_read_timeout 240s;
}
}
- https 证书申请
微信支付的牵涉网络安全问题,因此需要使用 https 协议。https 需要申请证书,可以购买第三方机构的证书。这里使用网上一个免费的证书,证书官网:https://letsencrypt.org/。官方有相关的申请安装说明。这里使用一个开源的脚本库代码实现。
# https://github.com/acmesh-official/acme.sh
curl https://get.acme.sh | sh # 安装
source ~/.bashrc # 重新加载环境变量
# 此处需要注意设置你的域名是能访问到的
# 会在网站项目中创建文件夹.well-known
# http://www.your-app.com/.well-known/
acme.sh --issue -d www.your-app.com -w /home/web/www/pay-app-final/public
# 这步执行完成之后会把申请成功的证书放在~/.acme.sh/目录里
# 将证书安装到网站的路径下
acme.sh --installcert -d www.your-app.com \\
--keypath /home/web/www/ssl/app-pay.wechat.com.key \\
--fullchainpath /home/web/www/ssl/app-pay.wechat.com.key.pem \\
--reloadcmd "sudo service nginx force-reload"
# 生成 dhparam.pem 文件,如果没有此文件,你的网站安全级别会被评为B
openssl dhparam -out /home/web/www/ssl/dhparam.pem 2048
# 修改nginx启用SSL
http {
# 新增
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# 兼容其他老浏览器的 ssl_ciphers 设置请访问 https://wiki.mozilla.org/Security/Server_Side_TLS
server {
listen 80 default_server;
# 新增
listen 443 ssl;
ssl_certificate /home/web/www/ssl/app-pay.wechat.com.key.pem;
ssl_certificate_key /home/web/www/ssl/app-pay.wechat.com.key;
# ssl_dhparam
ssl_dhparam /home/web/www/ssl/dhparam.pem;
# 其他省略
}
}
# 检查nginx的配置并重新启动
sudo service nginx configtest
sudo service nginx restart
该证书有效期是 90 天,需要定期 renew
重新申请,这部分 acme.sh
已经做了,在安装的时候往 crontab 增加了一行每天执行的命令
crontab -l # 查看定时任务
- 自动部署脚本
# 写脚本直接远程执行命令
# << deploy
# ... 这部分内容为远程执行的脚本,根据需要直接编写
# deploy这个名字是自己起的 叫什么都行
# deploy
ssh web@ip << deploy
cd /home/web/www/pay-app-final
git pull
npm i
pm2 restart pay-app
exit
deploy
- 代码远程调试
# 使用ssh的远程代理功能,把对远程服务器的请求映射到本地端口,对授权登录、支付回调等操作做远程调试
# v 显示详细的调试信息
# n 重定向stdin 到 /dev/null
# N 不要求分配shell,有些场景下ssh禁止账号请求shell终端
# t 强制配置 pseudo-tty
# R 远程转发
ssh -vnNtR 3003:localhost:3003 web@ip
完整代码
## .wxconfig.js
const appid = ""; // 公众账号id
const appsecret = ""; // 公众账号密钥
const mchid = ""; // 商户号
const mchKey = ""; // 商户密钥
const notifyUrl = ""; // 支付成功的回调地址
module.exports = {
appid,
appsecret,
mchid,
mchKey,
notifyUrl
};
## .aliconfig.js
const aliAppId = ""; // 商户号
const aliNotifyUrl = ""; // 异步通知地址
const aliReturnUrl = ""; // 同步跳转地址
const alipayPublicKey = ""; // 支付宝公钥
module.exports = {
aliAppId,
aliReturnUrl,
aliNotifyUrl,
alipayPublicKey
};
## app.js
const express = require("express");
const mongoose = require("mongoose");
const xml2js = require("xml2js");
const fs = require("fs");
const cookieParser = require("cookie-parser");
const AliPaySDK = require("alipay-sdk").default;
const AliPayFormData = require("alipay-sdk/lib/form").default;
const {
signPayParams,
getJsAPITicket,
signParams,
appid,
fullUrl,
getOauthUrl,
getOpenId
} = require("./utils/wx");
const {
aliAppId,
aliNotifyUrl,
aliReturnUrl,
alipayPublicKey
} = require("./.aliconfig.js");
const Order = require("./models/order");
const app = express();
const alipaySdk = new AliPaySDK({
appId: aliAppId,
privateKey: fs.readFileSync("./.alipay_private_key.pem", "ascii"),
alipayPublicKey
});
app.use("/", express.static("./public"));
app.use(cookieParser());
app.use(express.json());
app.use(
express.urlencoded({
extended: false
})
);
app.set("views", "./views"); // 设置模版文件的路径
app.set("view engine", "ejs"); // 设置使用的模版引擎
app.get("/", (req, res) => {
res.send("hello world! 这是一个微信支付的页面");
});
app.get("/wx_pay", async(req, res) => {
if (req.cookies.openid) {
const nonceStr = Date.now().toString();
const timestamp = Math.floor(Date.now() / 1000);
const jsTicket = await getJsAPITicket();
const signResult = signParams({
jsapi_ticket: jsTicket,
noncestr: nonceStr,
timestamp,
url: fullUrl(req)
});
res.render("wx_pay.ejs", {
appid,
timestamp,
nonceStr,
signResult,
openid: req.cookies.openid
});
} else {
// 重定向到微信授权页面
if (req.query.code) {
// 此处有code 获取openid
const { openid } = await getOpenId(req.query.code);
res.cookie("openid", openid, {
path: "/"
});
res.redirect("/wx_pay");
} else {
// 重定向到微信授权页面
const redirectUrl = fullUrl(req);
res.redirect(getOauthUrl(redirectUrl));
}
}
});
app.use("/api/v1/wechats", require("./api/v1/wechats"));
// 支付结果通知
app.post("/pay/notify_wx", async(req, res) => {
var buf = "";
req.on("data", chunk => {
buf += chunk;
});
req.on("end", async() => {
const payResult = await xml2js.parseStringPromise(buf, {
explicitArray: false
});
try {
if (payResult.xml.return_code == "SUCCESS") {
const paramsNeedSign = {};
for (let k in payResult.xml) {
if (k != "sign") {
paramsNeedSign[k] = payResult.xml[k];
}
}
const sign = signPayParams(paramsNeedSign);
if (sign == payResult.xml.sign) {
const orderNo = payResult.xml.out_trade_no;
const order = await Order.findOne({
order_no: orderNo
});
if (order) {
order.payed = true;
order.payed_time = Date.now();
await order.save();
}
}
}
res.send(`<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>`);
} catch (err) {
res.json(err);
}
});
});
app.use("/api/v1/orders", require("./api/v1/orders"));
app.get("/ali_pay_m", async(req, res) => {
const order = await Order.findOne({
order_no: req.query.order_no
});
if (order) {
const formData = new AliPayFormData();
formData.addField("notifyUrl", aliNotifyUrl);
formData.addField("returnUrl", aliReturnUrl);
formData.addField("bizContent", {
productCode: "QUICK_WAP_WAY",
subject: "node下单",
totalAmount: order.fee,
outTradeNo: order.order_no,
quit_url: "https://" + req.get("host")
});
let result = "";
try {
result = await alipaySdk.exec(
"alipay.trade.wap.pay", {}, {
formData
}
);
} catch (err) {
console.log(err);
}
res.render("ali_pay_pc.ejs", {
result
});
} else {
res.send("订单不存在");
}
});
app.get("/ali_pay_pc", async(req, res) => {
const order = await Order.findOne({
order_no: req.query.order_no
});
if (order) {
const formData = new AliPayFormData();
formData.addField("notifyUrl", aliNotifyUrl);
formData.addField("returnUrl", aliReturnUrl);
formData.addField("bizContent", {
productCode: "FAST_INSTANT_TRADE_PAY",
subject: "node下单",
totalAmount: order.fee,
outTradeNo: order.order_no
});
let result = "";
try {
result = await alipaySdk.exec(
"alipay.trade.page.pay", {}, {
formData
}
);
} catch (err) {
console.log(err);
}
res.render("ali_pay_pc.ejs", {
result
});
} else {
res.send("订单不存在");
}
});
// 支付宝同步通知
app.get("/pay_success", async(req, res) => {
let order = {};
if (alipaySdk.checkNotifySign(req.query)) {
const orderNo = req.query.out_trade_no;
if (orderNo) {
order = await Order.findOne({
order_no: orderNo
});
if (order) {
if (!order.payed) {
order.payed = true;
order.payed_time = Date.now();
await order.save();
}
}
}
}
res.render("pay_success.ejs", {
order
});
});
// 支付宝异步通知
app.post("/pay/notify_ali", async(req, res) => {
const isValid = alipaySdk.checkNotifySign(req.body);
if (isValid) {
const order = await Order.findOne({
order_no: req.body.out_trade_no,
payed: false
});
if (order) {
order.payed_time = Date.now();
order.payed = true;
await order.save();
}
res.send("success");
} else {
res.send("fail");
}
});
app.listen(3003, () => {
console.log("server is running on 3003");
});
mongoose
.connect("mongodb://localhost:27017/cat-shop", {
useNewUrlParser: true
})
.then(res => {
console.log("数据库连接成功");
})
.catch(err => {
console.log(err);
});
## wx.js 对接微信开发平台的代码封装
const axios = require("axios").default;
const crypto = require("crypto");
const qs = require("qs");
const xml2js = require("xml2js");
const moment = require("moment");
const sha1 = require("sha1");
const url = require基于node.js的web框架express