# 支付
对于大多数网站或者 APP 都是有着支付功能的。这一节我们将在项目中集成支付宝支付,为什么会选择支付宝呢。支付宝有一个沙箱环境,可以让我们不需要拥有真实的商家账号就可以进行支付的开发测试。本篇将带大家学习一下怎么集成支付宝并支付。

# 沙箱环境
首先访问 蚂蚁金服 (opens new window) ,然后用你的支付宝账号登录之后会看到如下界面:

点击 设置应用公钥

点击 查看秘钥 生成方法

点击查看密钥生成方法之后会跳转到一篇文档,里面可以下载 RSA2 密钥生成工具,请根据自己的系统下载对应的版本并打开:

如上图,密钥格式 选择 PKCS1,密钥长度 选择 RSA2,然后点击 生成密钥 按钮。生成完毕之后点击 开发助手 右侧的 复制公钥 按钮,将其内容粘贴到刚刚网页上的框中点击保存。

将支付宝公钥复制下来。
# 安装插件
蚂蚁金服开放平台 SDK 使用文档 (opens new window),建议大家必须要看一下。它会将必要的参数与加密信息处理好,我们只需要传入业务参数就可以了。
$ npm install alipay-sdk --save
# 配置参数
https://github.com/push-over/egg-example/blob/master/config
在这里我们需要用到支付宝开放平台开发助手生成的支付宝 应用私钥 和 支付宝公钥, 是支付宝公钥,大家不要弄混了哈。这两段配置实在是太长了,把它直接放到我们的配置文件中肯定是不合适的,我们新建一个 keys 目录,并创建两个文件。
└── config
└── keys // 密钥存放文件夹
├── alipay_public_key.pem // 支付宝公钥
└── app_priv_key.pem // 应用私钥
接下来我们来使用这两个文件:
config.alipay = {
appId: 'xxxxxx', // 支付宝分配给开发者的应用ID
privateKey: read('./keys/app_priv_key.pem'),
alipayPublicKey : read('./keys/alipay_public_key.pem'),
gateway: 'https://openapi.alipaydev.com/gateway.do', // 环境
return_url: 'http://127.0.0.1:7001/alipay/alipayReturn', // 一般是指客户端的一个页面
notify_url: 'http://requestbin.net/r/13ip1wr1', // 回调地址
};
read 是一个读取文件的方法。
const read = (filename: string) => {
return fs.readFileSync(path.resolve(__dirname, filename), 'ascii');
};
# 编写逻辑
https://github.com/push-over/egg-example/blob/master/app/service/Alipay.ts
那么接下来就上一个完整的示例进行整体的演示,如何请求 alipay.trade.page.pay (PC 端的支付接口)。
# 实例化操作
我们先来创建一个用来支付的类,并在构造函数中进行初始化操作:
import { Service, Context } from 'egg';
import AlipaySdk from 'alipay-sdk'; // 引入 SDK
/**
* Alipay Service
*/
export default class Alipays extends Service {
private alipaySdk: any;
constructor(ctx: Context) {
super(ctx);
// 获取配置参数
const { appId, privateKey, alipayPublicKey, gateway } = ctx.app.config.alipay;
// 实例化 AlipaySdk
this.alipaySdk = new AlipaySdk({
privateKey,
alipayPublicKey,
appId,
gateway,
});
}
}
# 请求接口
因为这个接口属于页面类接口,页面类接口默认返回的数据为 html 代码片段。这类接口我们需要创建一个 FormData 去请求。而这个 SDK 提供了一个 AlipayFormData 可以方便我们的创建:
import AlipayFormData from 'alipay-sdk/lib/form'; // 引入AlipayFormData
...
public async doPay(data: IApipayData) {
const { ctx, alipaySdk } = this;
const { return_url, notify_url } = ctx.app.config.alipay;
const data1 = {
product_code: 'FAST_INSTANT_TRADE_PAY', // 销售产品码 注:目前仅支持FAST_INSTANT_TRADE_PAY
out_trade_no: new Date().valueOf(), // 商户订单号 实际情况中,订单号按照具体需求生成
};
try {
const formData = new AlipayFormData();
formData.setMethod('get'); // 请求方式
formData.addField('notify_url', notify_url); // 支付完成后,支付宝主动向我们的服务器发送回调的地址
formData.addField('return_url', return_url); // 支付完成后,当前页面跳转的地址
formData.addField('biz_content', { ...data, ...data1 }); // 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递
return await alipaySdk.exec('alipay.trade.page.pay', {}, {
formData,
validateSign: true,
});
} catch (error) {
throw error;
}
}
请求成功后,会给我们一个用于跳转支付链接的 URL,前端拿到支付链接后去做跳转。在这里要特别注意,支付宝在用户付款完成后,会向我们的服务器发送一条 POST 方式 的异步回调。
# 支付回调
- 支付宝的支付回调分为 前端回调 和 服务器回调。
- 前端回调 是指当用户支付成功之后支付宝会让用户浏览器跳转回项目页面并带上支付成功的参数,也就是说前端回调依赖于用户浏览器,如果用户在跳转之前关闭浏览器,将无法收到前端回调。
- 服务器回调 是指支付成功之后支付宝的服务器会用订单相关数据作为参数请求项目的接口,不依赖用户浏览器。
因此我们判断支付是否成功要以服务器端回调为准。
由于这个异步通知是在支付完成之后,是支付宝以 POST 的方式请求我们的接口,这里我们要先定义一个路由 (opens new window):
// 支付成功异步通知
router.post('/alipay/alipayNotify', controller.alipay.alipayNotify);
接下来我们来编写控制器中的这个方法,由于我们的逻辑都是放到 service 层去处理的,在这里我们就做了一个转发的操作:
public async alipayNotify() {
const { ctx } = this;
const params = ctx.request.body;
await ctx.service.alipay.alipayNotify(params);
}
在通知里有一个 trade_status 参数,如果等于 TRADE_SUCCESS 就表示支付成功。然后你就可以继续进行你的后续操作,在这里我是使用了 redis 的消息队列,纯属是为了演示, lpush 命令将一个或多个值插入到列表头部。
public async alipayNotify(params: any) {
const { ctx } = this;
if (params.trade_status === 'TRADE_SUCCESS') {
// 后续操作
await ctx.service.redis.lpush('payInfo', params);
}
}
这个回调地址必须是外网可以访问到的,也就是说这一过程我们必须在线上开发。接下来我给大家介绍一个工具以便完成我们在本地开发接收异步回调
# RequestBin
是一个免费开源的网站,任何人都可以在上面申请一个专属的 URL(通常有效期 48 小时),对这个 URL 的任何类型的请求都会被记录下来,URL 的创建者可以看到请求的具体信息,包含请求时间、请求头、请求具体数据等。

系统就会给你分配一个 URL,把这个 URL 复制下来,放到我们之前放服务器端回调地址的参数上:

# 编写服务
在完成上述内容后,接下来我们来编写支付的服务。其实大家在掌握之前几篇文章的内容后,我相信编写一个服务对你来说还是轻而易举的事情。
# 定义类型
https://github.com/push-over/egg-example/blob/master/app/graphql/alipay/schema.graphql
实际情况还是看需求和支付接口的参数来定。其中订单描述不是必须的,订单标题和订单金额是必填的。
input Pay {
# 订单描述
body: String
# 订单标题
subject: String!
# 订单总金额
total_amount: String!
}
# 执行逻辑
https://github.com/push-over/egg-example/blob/master/app/graphql/alipay/connector.ts
调用我们之前编写好的逻辑,这就返回的就是支付的跳转链接:
...
export default class AlipayConnector {
...
// 这里传递的参数必须要符合我们定义的接口类型
public async pay(data: IApipayData) {
const { ctx } = this;
return await ctx.service.alipay.doPay(data); // 调用服务
}
}
# 数据操作
https://github.com/push-over/egg-example/blob/master/app/graphql/alipay/resolver.ts
基于前面的定义的支付 Schema ,我们可以写出如下的数据操作:
export default {
Mutation: {
pay(_root: any, { data }, { connector }) {
return connector.alipay.pay(data);
},
},
};
type Mutation {
pay(data: Pay) : String! // 这里我们只返回一个 URL
}
# 走一遍流程
打开我们的开发者工具,输入以下查询:

把这个链接复制下来,在浏览器中打开:

选择登陆账户付款,会让你输入账号密码进行支付。这个时候我们打开沙箱环境,左侧有一个沙箱账号。找到买家信息,我们使用这个账号登陆测试,还可以很方便的充值,享受一波消费的快感。

在支付成功后,会看到这个页面,大概 3s 左右会跳到你定义的的通知页面:

接下来打开我之前给大家推荐的 RequestBin 网站,刷新一下会看到:

把 RAW BOOY 完整的复制下来,放到 -d'' 中,然后在终端使用 curl 来请求我们的服务器端回调 URL:
curl -XPOST http://127.0.0.1:7001/alipay/alipayNotify -d'RAW BOOY'

可以看到我本地获取到了通知。我为什么会有这个呢。这是因为我在项目启动时就开启了 redis 的消息队列,也是为了给方便给大家做演示。

怎么在启动时添加我们的一些操作呢,接下来我们来学习一个知识点。
# 启动自定义
我们常常需要在应用启动期间进行一些 初始化 (opens new window) 工作,等初始化完成后应用才可以启动成功,并开始对外提供服务。
框架提供了统一的入口文件(app.ts)进行启动过程自定义,这个文件返回一个 Boot 类,我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。
框架提供了这些 生命周期函数 (opens new window)供开发人员处理:
- 配置文件即将加载,这是最后动态修改配置的时机(configWillLoad)
- 配置文件加载完成(configDidLoad)
- 文件加载完成(didLoad)
- 插件启动完毕(willReady)
- worker 准备就绪(didReady)
- 应用启动完成(serverDidReady)
- 应用即将关闭(beforeClose)
我们可以在 app.ts 中定义这个 Boot 类,然后我们在 didReady中启用我们的消息队列:
import { Application } from 'egg';
export default class AppBootHook {
app: Application;
constructor(app: Application) {
this.app = app;
}
// 应用已经启动完毕
async didReady() {
const ctx = await this.app.createAnonymousContext();
await ctx.service.alipay.paymentRedisPubSub(); // 启动消息队列
}
}
我们来看一下这个方法,Redis 这个类,因为比较简单,就没有给大家做演示,大家如果有兴趣可以拉取我的源码下来进行查看。
public async paymentRedisPubSub() {
const { ctx } = this;
let num = 0;
// tslint:disable-next-line: no-constant-condition
while (true) {
const result = await ctx.service.redis.brpop('payInfo');
num++;
// 处理各种事宜
console.log(num, '==============', result);
}
}
到这,我们的接入支付宝进行支付就结束了,本篇文章主要讲的是 pc 页面进行支付。大家可以尝试接入其他接口测试。
# 小结
本篇文章主要给大家讲解了怎么接入支付宝并进行支付以及怎么在本地接收异步通知。微信也是一样的,只不过那个需要商家账号,无缘给大家做演示。相信大家只要掌握了怎么对接支付宝,微信或者别的对你来说也不是难事。