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模板,生成前端页面

服务器环境配置

  1. 复制本地公钥到远程服务器
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								# 远程登录
  1. 服务器软件安装
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模块
  1. 生成 ssh 密钥
ssh-key-gen 							# 在服务器上生成一个密钥,用于拉取仓库代码
  1. 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;
    }
}
  1. 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 										# 查看定时任务
  1. 自动部署脚本
# 写脚本直接远程执行命令
# << deploy
#    ... 这部分内容为远程执行的脚本,根据需要直接编写
#    deploy这个名字是自己起的 叫什么都行
# deploy
ssh web@ip << deploy
  cd /home/web/www/pay-app-final
  git pull
  npm i
  pm2 restart pay-app
  exit
deploy
  1. 代码远程调试
# 使用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 整合 Express 框架实现微信支付和支付宝支付的主要内容,如果未能解决你的问题,请参考以下文章

基于node.js的web框架express

Node.js Express 框架

前端node.js框架node.js框架express

Node.js Express框架 详细总结

Express框架

配置node.js中的express框架