一些重要的支付宝沙箱文档及网址
- 支付宝开放平台:https://open.alipay.com/
- 简单介绍:https://www.yuque.com/chenqiu/alipay-node-sdk/guide
- 支付宝开放平台API:https://open.alipay.com/api
- 支付宝开放平台文档:https://opendocs.alipay.com/home
支付流程介绍
图片来自支付宝开放平台。
上面的流程图,已经大致描述了发起支付的过程,这里笔者用自己的语言,详细地转化一下:
下单。这里的下单,在项目中往往表现为用户确认了订单、然后选择支付方式、点击立即支付,即当用户点击“立即支付”时才产生的动作。 这一步要发送一个请求到我们的服务器,假如说系统中立即支付的API路径是/pay,参数一般就是订单包含的订单项,如商品id、购买数量、优惠券id等。
服务器端处理用户的下单请求。在这一步,是业务比较多的一步,也是关键步骤。我们根据客户端传来的商品id、数量、优惠券等参数,计算出每一个商品的单价、订单的总金额等(金额是不应该由客户端直接传的,应该由服务器端计算,更安全)。再生成一个订单号。然后调用一个接口,把这些参数(如商品名称、支付金额、订单号等)打包传给支付宝,也就是对应上图中的1.1。
依然是处理用户的下单请求,只是我嫌段落太长,换一段。在图中的1.1,我们调用了支付宝的接口。其实图中还少了一步1.2,那就是支付宝会返回一段html文档,就是大家经常看到的带二维码的PC端支付页面。请注意,到这一步,对客户端来说依然是在访问/pay接口,因为我们还没有对请求作出响应,只是一直在和支付宝交互罢了。所以,我们把支付宝返回的html文档作为响应信息,返回给客户端,客户端直接加载这一段html文档,用户就看到了一个支付页面。
到了这里,对应上图中的2、3、4、5步,和我们的服务器就没关系了,完全是用户在进行支付的过程。
到了第6步,用户支付完成,支付宝会跳转到一个我们指定的网页,这个网页一般是我们自定义的页面,比如在网页上显示“您已成功购买xxx商品,感谢您的支持”等。注意,此处的returnUrl只是跳转到我们自定义的友好页面,告诉用户支付成功了,只做展示用途,并非真正的支付回调。
到了第7步,支付宝会调用我们指定的回调API接口,将一些参数传给我们,如交易流水号、订单号,我们可以根据这些信息查询交易是否真的成功了(第8步),从而执行后续的业务,比如将订单状态变为已支付、给用户增加积分、扣减优惠券……等。
沙箱环境简介与配置
支付宝有一个供开发者测试使用的沙箱环境,会提供一个沙箱版的支付宝app、一个商家账户、一个买家账户。有了这个,可以让我们跳过商家入驻、企业资质审核等过程,开箱即用,降低了学习成本。
现在,让我们简单的配置沙箱环境。
1、进入到支付宝支付官网,用你自己的支付宝扫码登录,再点击进入管理中心。
2、点击进入沙箱应用。
此时就到了沙箱应用的控制台,进入之后要创建一个应用。
3、生成安全密钥
此部分的官方文档为:https://opendocs.alipay.com/common/02kipl。
进入该网址下载支付宝官方的密钥生成工具应用。
进入应用后,根据开发语言选择密钥格式和密钥长度。注意此处要选择RSA2。
然后点击生成密钥,工具会自动生成商户应用公钥(public_key)和应用私钥(private_key)。
这里有几个密钥要注意一下:
- 应用公钥(public key):需提供给支付宝账号管理者上传到支付宝开放平台。
- 应用私钥(private key):由开发者自己保存,需填写到代码中供签名时使用。
- 支付宝公钥:支付宝提供的公钥,用于接入服务。
还有要注意的事情为:
- 生成的私钥需妥善保管,避免遗失,不要泄露。
- 密钥和应用(APPID)一一对应,即开发者需要为名下的每个应用分别设置密钥,且不同应用的密钥不能混用。
4、配置安全密钥
用上述的工具生成密钥之后,要把应用公钥上传到支付宝开发者平台,如图点击切换为自定义密钥。
在弹出的窗口,把刚才生成的应用公钥复制粘贴进去,点击确定,就可以看到这个界面了,注意这里的支付宝公钥:
5、下载支付宝沙箱APP及账号管理
“支付宝沙箱APP”可以在官网找到,直接下载即可。
在沙箱控制台当中,点击“沙箱账号”,就可以看到当前应用对应的沙箱账号,用来登录我们的沙箱版支付宝APP进行测试。
上面的是商家的账号,下面是买家的测试账号。
6、学会看文档
在支付宝开放平台,点击文档,即可查看文档。如果要查看API就点击API,如果要查看技术的总体介绍,就点击文档。
比如点击API,然后选择场景:
比如说选择电脑网站支付,就可以查看到这些API列表。点击后面的查看文档就可以查看到这些API详细的参数。
其实不同场景的API是差不多的,例如“下单并支付页面”的接口,电脑网站支付的API接口路径为“alipay.trade.page.pay”,而手机端的则为“alipay.trade.wap.pay”。
7、开始写代码
下载依赖
npm install alipay-sdk
封装SDK
appId在上面进入沙箱控制台的时候就可以看到了。
注意下面的支付宝公钥和应用私钥,都是步骤二里的。
const AlipaySdk = require('alipay-sdk').default; // 引入 SDK
const alipaySdk = new AlipaySdk({
appId: '此处要修改', // 开放平台上创建应用时生成的 appId
signType: 'RSA2', // 签名算法,默认 RSA2
gateway: 'https://openapi.alipaydev.com/gateway.do', // 支付宝网关地址 ,沙箱环境下使用时需要修改 正式线上的时候换成 https://openapi.alipay.com/gateway.do
alipayPublicKey: '此处要修改', // 支付宝公钥,需要对结果验签时候必填
privateKey: '此处要修改', // 应用私钥字符串
});
module.exports = alipaySdk;
//正式环境只要把上述换成正式的就可以了
创建提交交易信息的表单
注意这里要编写的参数是在上面的API文档里可以查看到。
const AlipayFormData = require("alipay-sdk/lib/form").default;
const formData = new AlipayFormData();
formData.setMethod('get');
formData.addField('bizContent', {
outTradeNo: '15693801273225', // 商户订单号,64个字符以内、可包含字母、数字、下划线,且不能重复
productCode: 'FAST_INSTANT_TRADE_PAY', // 销售产品码,与支付宝签约的产品码名称,仅支持FAST_INSTANT_TRADE_PAY
totalAmount: '0.01', // 订单总金额,单位为元,精确到小数点后两位
subject: '商品', // 订单标题
body: '商品详情', // 订单描述
});
// 表示异步通知回调,
formData.addField('notifyUrl', 'https://www.baidu.com');
// 付款成功的跳转页面
formData.addField('returnUrl', 'https://www.bilibili.com');
这里的notifyUrl和returnUrl要做一个区分:
- notifyUrl:消息回调的接口,当付款成功,支付宝会发送成功信息到我们的项目当中,告诉我们付款成功了,即我们项目自己定义的接口,需要公网能够访问到的地址,即**项目要上线。**如果是自己本地的测试项目,可以配置内网穿透。
- returnUrl:付款成功之后,页面会自动跳转的嘛,returnUrl就表示这个跳转的地址。
发送交易请求:调用SDK的exec()方法
该方法有三个参数:
const result = await alipaySdk.exec(method, params, options);
- method:String 调用的 Api,比如 alipay.system.oauth.token
- params:Api 的请求参数(包含“公共请求参数”和“业务参数”)
- options:可以传入我们的formData
- 更详细请查看:https://www.yuque.com/chenqiu/alipay-node-sdk/exec
具体实例:
// 付款API:https://opendocs.alipay.com/apis/api_1/alipay.trade.pay?scene=32
async function test() {
const result = await alipaySdk.exec( // result 为可以跳转到支付链接的 url
'alipay.trade.page.pay', // 统一收单下单并支付页面接口
{}, // api 请求的参数(包含“公共请求参数”和“业务参数”)
{
formData: formData
},
);
console.log(result)
}
test()
到这里,我们查看一下这个result是什么?
把这个复制到浏览器里,咱们就来到了支付界面啦,这里就可以登录我们的沙箱买家测试账号进行付款了:
代码跳转的话代码如下:
goPay() {
let data = {
orderId: 't454545212121' //随机生成唯一的就行了这个 自己找吧
}
var instance = this.$axios.create({headers: {'content-type': 'application/x-www-form-urlencoded'}});
// 代理到 http://localhost:3000/api/pcpay
instance.post(`http://localhost:3000/api/pcpay`, this.$qs.stringify(data)).then(res =>{
this.data=res;
window.open(res.data.result)
});
}
付款完毕之后,就会跳转到returnUrl的网址喽。
有没有发现method就是API文档里的接口名称?没错!
咱们上面调用了“alipay.trade.page.pay”这个接口来下单与付款,下面我们再来看看如何用“alipay.trade.query”查看这个订单的状态。
async function check() {
let outTradeNo = "15693801273225";
const formData = new AlipayFormData();
formData.setMethod('get');
formData.addField('bizContent', {
outTradeNo
});
// 通过该接口主动查询订单状态
// 查询订单状态:https://opendocs.alipay.com/apis/api_1/alipay.trade.query?scene=23
let result = await alipaySdk.exec(
'alipay.trade.query', {}, {
formData: formData
},
);
console.log(result)
}
check()
这里的result还是一个网址,我们需要axios请求这个网址,才能查看订单状态:
/**
* 添加购物车提交订单支付宝支付后查询订单状态是否成功 */
router.post('/api/member/queryOrderAlipay', (req, res) => {
let orderId=req.body.orderId
const formData = new AlipayFormData();
formData.setMethod('get');
formData.addField('bizContent', {
orderId
});
// 通过该接口主动查询订单状态
const result = alipaySdk.exec(
'alipay.trade.query',
{},
{ formData: formData },
);
axios({
method: 'GET',
url: result
})
.then(data => {
let r = data.data.alipay_trade_query_response;
if(r.code === '10000') { // 接口调用成功
switch(r.trade_status) {
case 'WAIT_BUYER_PAY':
res.send(
{
"success": true,
"message": "success",
"code": 200,
"timestamp": (new Date()).getTime(),
"result": {
"status":0,
"massage":'交易创建,等待买家付款'
}
}
)
break;
case 'TRADE_CLOSED':
res.send(
{
"success": true,
"message": "success",
"code": 200,
"timestamp": (new Date()).getTime(),
"result": {
"status":1,
"massage":'未付款交易超时关闭,或支付完成后全额退款'
}
}
)
break;
case 'TRADE_SUCCESS':
res.send(
{
"success": true,
"message": "success",
"code": 200,
"timestamp": (new Date()).getTime(),
"result": {
"status":2,
"massage":'交易支付成功'
}
}
)
break;
case 'TRADE_FINISHED':
res.send(
{
"success": true,
"message": "success",
"code": 200,
"timestamp": (new Date()).getTime(),
"result": {
"status":3,
"massage":'交易结束,不可退款'
}
}
)
break;
}
} else if(r.code === '40004') {
res.send('交易不存在');
}
})
.catch(err => {
res.json({
msg: '查询失败',
err
});
});
})
状态码可以通过上面API文档查看:
参考链接
- https://blog.csdn.net/rapguys/article/details/115763799
- https://blog.51cto.com/lllomh/3037337