概述
- 接入支付宝
- 必须注册企业支付宝账户
- 支付宝开发接入页面:https://open.alipay.com/developmentAccess/developmentAccess.html
- 点击支付应用
- 填写对应应用名称, 点击创建
- 创建以后进入概览页面, 上传应用图标
- 设置应用公钥,提交审核
- 接口加密签名
- 下载签名工具:https://opendocs.alipay.com/open/291/106097/
- 选择非Java(java语言还是选择java适用),点击生成密钥,会生成两个:商户应用私钥,商户应用公钥
- 复制应用公钥,在支付宝后台开发设置中 设置应用公钥 粘贴进去,保存后会生成一个支付宝公钥
- 签名。并保存好私钥、公钥
- 配置签名 提交审核
- 审核周期为1天
- 官方支付流程
- 官方支付流程文档:https://docs.open.alipay.com/203/107084/
- 支付宝支付官方文档
- https://docs.open.alipay.com/270/105899/
- Nodejs 支付宝支付实现步骤
- 1、登录支付宝开放平台 获取应用 APPID、获取支付宝公钥、以及 RSA 签名的应用私钥
- 注意:RSA 签名验签工具可以生成应用私钥和应用公钥,我们在支付宝开放平台填写应用公钥生成支付宝公钥
- 2、调用 nodejs 支付宝支付 sdk 实现支付
- Nodejs Aliapy Sdk 文档:https://github.com/Luncher/alipay
- 3、 Aliapy Sdk 的使用:
- 安装模块 $
npm i alipay-mobile -S
- 引入模块 $
const Alipay = require('alipay-mobile')
- 配置开放平台 appid、 你的应用私钥、蚂蚁金服支付宝公钥
- $
const options = {app_id: '', appPrivKeyFile: "应用私钥", alipayPubKeyFile: "支付宝公钥"}
- $
- 实例化 Alipay $
const service = new Alipay(options);
注意这里版本不同,可能实现方式不一样,参考issue - 配置支付订单的信息、以及配置支付参数
- 生成支付宝支付跳转地址
- 处理异步通知
- 安装模块 $
- 1、登录支付宝开放平台 获取应用 APPID、获取支付宝公钥、以及 RSA 签名的应用私钥
代码流程
eggjs中的config配置
支付配置如下:
// 支付相关配置:微信,支付宝
config.pay = {
ali: {
options: {
app_id: '', // 支付宝应用id
appPrivKeyFile: '', // 应用私钥 字符串即可,文件需要读取同样是字符串
alipayPubKeyFile: '' // 支付宝公钥
},
// 注意这里的路由是之前配置好的,后面会有讲到
basicParams: {
return_url: 'http://127.0.0.1:7001/pay/ali/return', // 支付成功返回地址 此处仅作为举例 匹配路由 后期可配置调试环境、测试环境和线上环境 区分不同域名
notify_url: 'http://127.0.0.1:7001/pay/ali/notify' //支付成功异步通知地址 此处仅作为举例
}
},
// .... 当然这里还可以有其他支付,如微信等
}
关闭csrf验证如下:(注意如果不关闭,那么支付宝没法向我们的服务器post数据)
config.security = {
csrf: {
// 判断是否需要 ignore 的方法,请求上下文 context 作为第一个参数
ignore: ctx => {
// 屏蔽csrf验证的接口或路由
let arr = [
// ... 其他url
'/pay/ali/notify',
];
let flag = false;
// 进行匹配
arr.some((item) => {
if (ctx.request.url === item) {
// console.log(item);
flag = true;
return true;
}
});
return flag;
},
},
};
eggjs中的路由配置
// 路由片段示例 注意相关的中间件,不再config中配置全局的,直接在路由中引入,特别说明下这个xmlParseMiddleware中间件下面会有配置说明
router.get('/pay/ali', controller.web.pay.ali); // 支付宝支付
router.get('/pay/ali/return', controller.web.pay.aliReturn); // 支付宝支付成功回调
router.post('/pay/ali/notify', xmlParseMiddleware, controller.web.pay.aliNotify); // 支付成功异步通知 注意关闭csrf验证
编写service服务, 用于controller调用 app/service/pay.js
'use strict';
const Service = require('egg').Service;
const Alipay = require('alipay-mobile').default; // 这里是一个issue: https://github.com/Luncher/alipay/issues/49
/**
* 用于微信和支付宝相关的支付服务功能, 此处只展示支付宝代码
*/
class PayService extends Service {
/* ************************ 支付宝相关服务 ************************ */
async ali(orderData) {
return new Promise((resolve, reject) => {
//实例化 alipay
const service = new Alipay(this.config.pay.ali.options);
//获取返回的参数
service.createPageOrderURL(orderData, this.config.pay.ali.basicParams)
.then(result => {
console.log(result);
resolve(result.data);
});
})
}
// 验证异步通知的数据是否正确 params 是支付宝post给我们服务器的数据
aliNotify(params) {
//实例化 alipay
const service = new Alipay(this.config.pay.ali.options);
return service.makeNotifyResponse(params);
}
}
module.exports = PayService;
编写用于解析支付宝post给我们的xml文件的中间件 app/middleware/xmlparse.js
/*
egg中配置koa中间件,注意上面配置路由中引用了这个中间件,属于路由中间件而非全局配置的中间件
cnpm install koa-xml-body --save
*/
module.exports = require('koa-xml-body');
编写支付相关的controller控制器 app/controller/web/pay.js
'use strict';
const Controller = require('egg').Controller;
class PayController extends Controller {
/* ************************ 支付宝相关 ************************ */
async ali() {
let id = this.ctx.request.query.id;
let orderResult = await this.ctx.model.Order.find({ "_id": id });
if (!(orderResult && orderResult.length)) {
return this.ctx.redirect('/order/confirm?id=' + id); // 定位到当前页面, 或返回错误信息
}
const data = {
subject: '支付宝支付', // 这里显示什么,同微信支付,看需求
out_trade_no: id, // 必须是string类型
total_amount: orderResult[0].all_price
}
let url = await this.service.pay.ali(data);
this.ctx.redirect(url); // 这里跳转到支付宝 我的收银台 进行扫码支付或登录账户支付
}
// 这里是用户扫码之后的回调页面显示,扫码成功后会跳转到(重定向到)商户: /pay/alipay/return 这里
// 页面上会有一些携带参数,包括cartset,out_trade_no, method, total_amount, sign 等,但不能通过这些信息来更新我们自己数据库的订单,不安全
// 我们需要用支付宝给我们服务器推送过来的数据,来做更新处理
async aliReturn() {
this.ctx.body = '支付成功';
//接收异步通知 页面什么的自行处理 TODO
}
// 支付成功以后更新订单信息 必须正式上线, 或者起码配置一个测试的公网ip或域名, 用于接收付款成功后,支付宝给我们post的数据
async aliNotify() {
const params = this.ctx.request.body; // 接收 支付宝 post 的 XML 数据
// console.log(params);
let result = await this.service.pay.aliNotify(params); // 进行异步通知的数据验证
// console.log('-------------');
// console.log(result);
// 校验正确的时候
if (result.code === '0') {
if (params.trade_status == 'TRADE_SUCCESS') {
// 更新订单
}
} else {
// 如果校验失败 理论上要追踪记录一下的,记录至数据库或者存入日志系统
}
}
}
module.exports = PayController;
前端页面相关处理
也就是调用控制器中的ali这个方法的页面,这里是确认订单页面,也就是点击使用支付宝支付页面,关键代码,仅作示例展示
<div class="section section-payment">
<div class="cash-title" id="J_cashTitle">
选择以下支付方式付款
</div>
<div class="payment-box ">
<div class="payment-body">
<ul class="clearfix payment-list J_paymentList J_linksign-customize">
<!-- 微信支付暂时不考虑,下面只有支付宝 -->
<li class="J_bank" id="alipay">
<a target="_blank" href="/pay/ali?id=<%=id%>"> <img src="/public/web/image/payOnline_zfb.png" alt="支付宝" style="margin-left: 0;"></a>
</li>
</ul>
</div>
</div>
</div>
<div class="modal fade" id="alipayModel" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">正在支付...</h4>
</div>
<div class="modal-body">
<a href="#">已经支付成功</a>
<br>
<br>
<br>
<a href="#">未支付成功</a>
</div>
</div>
</div>
</div>
<script>
// 这里使用jQuery,当前前提要引入jQuery
$("#alipay").click(function() {
// 因为点击支付宝支付图片跳转到支付宝的我的收银台是打开一个新的页面,所以当前页面这里直接展示弹窗,后续如何处理,看业务需求
$('#alipayModel').modal('show');
});
// 这里有个轮询订单状态,支付成功后,跳转到用户订单页面,这里接口和路由都需要编写和配置,不做展示
var timer = setInterval(function() {
$.get('/order/getOrderPayStatus?id=<%=id%>', function(response) {
// console.log(response);
if (response.success) {
clearInterval(timer);
location.href = '/user/order'
}
});
}, 5000);
</script>