先看看我们的效果实现
运行我们的项目
node app.js
点击付款的按钮,弹出提示框,点击成功,跳转到支付宝网页支付环境
使用支付宝沙箱环境的买家账号进行支付
点击下一步
再支付一下支付密码就成功了,我们会发现我们的商家账号会多出0.99元,至于那0.01可能是支付宝收了手续费
上面主要是给大家演示一下,项目已经可以成功实现了,大家可以放心使用,接下来要做的工作了。
一、登录支付宝开放平台,并进入沙箱环境
2点击研发服务,进入沙箱环境
二、Coding
(1) 设置RSA2密钥
1.1 下载支付宝密钥生成器
1.2 选择密钥格式和长度后,生成密钥
1.3 将生成的公钥填入到沙箱环境的RSA2中
1.4 保存后点击查看,支付宝公钥
(2)Coding
这是我的一个目录结构
需要注意的是
F:\AliyunPay\routes\pem\alipay_public_key_nonjava.pem 这是支付宝公钥,不是应用公钥!!!!!!!获取支付宝公钥,在上面已经说了
F:\AliyunPay\routes\pem\app_private_key_nonjava.pem
是放你自己的应用私钥和你的支付宝公钥 ,此外【注意】将支付宝公钥 ,将上述生成的字符串添加到该文件alipay_public_key_nonjava.pem,并且字符串前后加上秘钥所需描述:“-----BEGIN PUBLIC KEY-----”、“-----END PUBLIC KEY-----”
【注意】将应用私钥 ,将上述生成的字符串添加到该文件app_private_key_nonjava.pem,并且字符串前后加上秘钥所需描述:“-----BEGIN RSA PRIVATE KEY-----”、“-----END RSA PRIVATE KEY-----”
-----BEGIN PUBLIC KEY-----
你的支付宝公钥
-----END PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY-----
你的应用私钥
-----END RSA PRIVATE KEY-----
从目录结构可以看出
有三个.js
alipay.js
var fs = require('fs');
var path = require('path');
var utl = require('./utl');
var alipay_gate_way = 'https://openapi.alipay.com/gateway.do';
var alipay_gate_way_sandbox = 'https://openapi.alipaydev.com/gateway.do';
module.exports = Alipay;
/**
*
* @param {Object} opts
* @param {String} opts.appId 支付宝的appId
* @param {String} opts.notifyUrl 支付宝服务器主动通知商户服务器里指定的页面http/https路径
* @param {String} opts.rsaPrivate 商户私钥pem文件路径
* @param {String} opts.rsaPublic 支付宝公钥pem文件路径
* @param {String} opts.signType 签名方式, 'RSA' or 'RSA2'
* @param {Boolean} [opts.sandbox] 是否是沙盒环境
* @constructor
*/
function Alipay(opts) {
this.appId = opts.appId;
this.sandbox = !!opts.sandbox;
this.notifyUrl = opts.notifyUrl;
this.signType = opts.signType;
this.rsaPrivate = fs.readFileSync(opts.rsaPrivate, 'utf-8');
this.rsaPublic = fs.readFileSync(opts.rsaPublic, 'utf-8');
}
var props = Alipay.prototype;
props.makeParams = function(method, biz_content) {
return {
app_id: this.appId,
method: method,
format: 'JSON',
charset: 'utf-8',
sign_type: this.signType,
timestamp: new Date().format('yyyy-MM-dd hh:mm:ss'),
version: '1.0',
biz_content: JSON.stringify(biz_content)
};
};
/**
* 生成支付参数供客户端使用
* @param {Object} opts
* @param {String} opts.subject 商品的标题/交易标题/订单标题/订单关键字等
* @param {String} [opts.body] 对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body
* @param {String} opts.outTradeId 商户网站唯一订单号
* @param {String} [opts.timeout] 设置未付款支付宝交易的超时时间,一旦超时,该笔交易就会自动被关闭。
当用户进入支付宝收银台页面(不包括登录页面),会触发即刻创建支付宝交易,此时开始计时。
取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。
该参数数值不接受小数点, 如 1.5h,可转换为 90m。
* @param {String} opts.amount 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
* @param {String} [opts.sellerId] 收款支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID
* @param {String} opts.goodsType 商品主类型:0—虚拟类商品,1—实物类商品 注:虚拟类商品不支持使用花呗渠道
* @param {String} [opts.passbackParams] 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
* @param {String} [opts.promoParams] 优惠参数(仅与支付宝协商后可用)
* @param {String} [opts.extendParams] 业务扩展参数 https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.3oJPAi&treeId=193&articleId=105465&docType=1#kzcs
* @param {String} [opts.enablePayChannels] 可用渠道,用户只能在指定渠道范围内支付。当有多个渠道时用“,”分隔。注:与disable_pay_channels互斥
* @param {String} [opts.disablePayChannels] 禁用渠道,用户不可用指定渠道支付。当有多个渠道时用“,”分隔。 注:与enable_pay_channels互斥
* @param {String} [opts.storeId] 商户门店编号
*/
props.pay = function (opts) {
var biz_content = {
body: opts.body,
subject: opts.subject,
out_trade_no: opts.outTradeId,
timeout_express: opts.timeout,
total_amount: opts.amount,
seller_id: opts.sellerId,
product_code: 'QUICK_MSECURITY_PAY',
goods_type: opts.goodsType,
passback_params: opts.passbackParams,
promo_params: opts.promoParams,
extend_params: opts.extendParams,
enable_pay_channels: opts.enablePayChannels,
disable_pay_channels: opts.disablePayChannels,
store_id: opts.storeId
};
var params = this.makeParams('alipay.trade.app.pay', biz_content);
params.notify_url = this.notifyUrl;
return utl.processParams(params, this.rsaPrivate, this.signType);
};
/**
* 生成支付参数供web端使用
* @param {Object} opts
* @param {String} opts.subject 商品的标题/交易标题/订单标题/订单关键字等
* @param {String} [opts.body] 对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body
* @param {String} opts.outTradeId 商户网站唯一订单号
* @param {String} [opts.timeout] 设置未付款支付宝交易的超时时间,一旦超时,该笔交易就会自动被关闭。
当用户进入支付宝收银台页面(不包括登录页面),会触发即刻创建支付宝交易,此时开始计时。
取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。
该参数数值不接受小数点, 如 1.5h,可转换为 90m。
* @param {String} opts.amount 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
* @param {String} [opts.sellerId] 收款支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID
* @param {String} opts.goodsType 商品主类型:0—虚拟类商品,1—实物类商品 注:虚拟类商品不支持使用花呗渠道
* @param {String} [opts.passbackParams] 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
* @param {String} [opts.promoParams] 优惠参数(仅与支付宝协商后可用)
* @param {String} [opts.extendParams] 业务扩展参数 https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.3oJPAi&treeId=193&articleId=105465&docType=1#kzcs
* @param {String} [opts.enablePayChannels] 可用渠道,用户只能在指定渠道范围内支付。当有多个渠道时用“,”分隔。注:与disable_pay_channels互斥
* @param {String} [opts.disablePayChannels] 禁用渠道,用户不可用指定渠道支付。当有多个渠道时用“,”分隔。 注:与enable_pay_channels互斥
* @param {String} [opts.storeId] 商户门店编号
* @param {String} [opts.return_url] 客户端回调地址,HTTP/HTTPS开头字符串
*/
props.webPay = function (opts) {
var biz_content = {
body: opts.body,
subject: opts.subject,
out_trade_no: opts.outTradeId,
timeout_express: opts.timeout,
total_amount: opts.amount,
seller_id: opts.sellerId,
product_code: 'FAST_INSTANT_TRADE_PAY',
goods_type: opts.goodsType,
passback_params: opts.passbackParams,
promo_params: opts.promoParams,
extend_params: opts.extendParams,
enable_pay_channels: opts.enablePayChannels,
disable_pay_channels: opts.disablePayChannels,
store_id: opts.storeId,
return_url: opts.return_url
};
var params = this.makeParams('alipay.trade.page.pay', biz_content);
params.notify_url = this.notifyUrl;
return utl.processParams(params, this.rsaPrivate, this.signType);
};
/**
* 签名校验
* @param {Object} response 支付宝的响应报文
*/
props.signVerify = function (response) {
var ret = utl.copy(response);
var sign = ret['sign'];
ret.sign = undefined;
ret.sign_type = undefined;
var tmp = utl.encodeParams(ret);
return utl.signVerify(tmp.unencode, sign, this.rsaPublic, this.signType);
}
/**
* 查询交易状态 https://doc.open.alipay.com/doc2/apiDetail.htm?spm=a219a.7629065.0.0.PlTwKb&apiId=757&docType=4
* @param {Object} opts
* @param {String} [opts.outTradeId] 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 tradeId,outTradeId如果同时存在优先取tradeId
* @param {String} [opts.tradeId] 支付宝交易号,和商户订单号不能同时为空
* @param {String} [opts.appAuthToken] https://doc.open.alipay.com/doc2/detail.htm?treeId=216&articleId=105193&docType=1
*/
props.query = function (opts) {
var biz_content = {
out_trade_no: opts.outTradeId,
trade_no: opts.tradeId
};
var params = {
app_id: this.appId,
method: 'alipay.trade.query',
format: 'JSON',
charset: 'utf-8',
sign_type: this.signType,
timestamp: new Date().format('yyyy-MM-dd hh:mm:ss'),
version: '1.0',
app_auth_token: opts.appAuthToken,
biz_content: JSON.stringify(biz_content)
};
var params = this.makeParams('alipay.trade.query', biz_content);
if(this.appAuthToken) {
params.app_auth_token = this.appAuthToken;
}
var body = utl.processParams(params, this.rsaPrivate, this.signType);
return utl.request({
method: 'GET',
url: (this.sandbox? alipay_gate_way_sandbox : alipay_gate_way) + '?' + body
});
};
/**
* 统一收单交易关闭接口 https://doc.open.alipay.com/doc2/apiDetail.htm?spm=a219a.7629065.0.0.6VzMcn&apiId=1058&docType=4
* @param {Object} opts
* @param {String} [opts.outTradeId] 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 tradeId,outTradeId如果同时存在优先取tradeId
* @param {String} [opts.tradeId] 支付宝交易号,和商户订单号不能同时为空
* @param {String} [opts.operatorId] 卖家端自定义的的操作员 ID
* @param {String} [opts.appAuthToken] https://doc.open.alipay.com/doc2/detail.htm?treeId=216&articleId=105193&docType=1
*/
props.close = function (opts) {
var biz_content = {
out_trade_no: opts.outTradeId,
trade_no: opts.tradeId,
operator_id: opts.operatorId
};
var params = this.makeParams('alipay.trade.close', biz_content);
if(this.appAuthToken) {
params.app_auth_token = this.appAuthToken;
}
var body = utl.processParams(params, this.rsaPrivate, this.signType);
return utl.request({
method: 'GET',
url: (this.sandbox? alipay_gate_way_sandbox : alipay_gate_way) + '?' + body
});
};
/**
* 统一收单交易退款接口 https://doc.open.alipay.com/doc2/apiDetail.htm?spm=a219a.7629065.0.0.PlTwKb&apiId=759&docType=4
* @param {Object} opts
* @param {String} [opts.outTradeId] 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 tradeId,outTradeId如果同时存在优先取tradeId
* @param {String} [opts.tradeId] 支付宝交易号,和商户订单号不能同时为空
* @param {String} [opts.operatorId] 卖家端自定义的的操作员 ID
* @param {String} [opts.appAuthToken] https://doc.open.alipay.com/doc2/detail.htm?treeId=216&articleId=105193&docType=1
* @param {String} opts.refundAmount 需要退款的金额,该金额不能大于订单金额,单位为元,支持两位小数
* @param {String} [opts.refundReason] 退款的原因说明
* @param {String} [opts.outRequestId] 标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传。
* @param {String} [opts.storeId] 商户的门店编号
* @param {String} [opts.terminalId] 商户的终端编号
*/
props.refund = function (opts) {
var biz_content = {
out_trade_no: opts.outTradeId,
trade_no: opts.tradeId,
operator_id: opts.operatorId,
refund_amount: opts.refundAmount,
refund_reason: opts.refundReason,
out_request_no: opts.outRequestId,
store_id: opts.storeId,
terminal_id: opts.terminalId
};
var params = this.makeParams('alipay.trade.refund', biz_content);
if(this.appAuthToken) {
params.app_auth_token = this.appAuthToken;
}
var body = utl.processParams(params, this.rsaPrivate, this.signType);
utl.request({
method: 'GET',
url: body
}).then(function (ret) {
console.log("***** ret.body=" + body);
});
};
/**
* 统一收单交易退款查询 https://doc.open.alipay.com/doc2/apiDetail.htm?docType=4&apiId=1049
* @param {Object} opts
* @param {String} [opts.outTradeId] 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 tradeId,outTradeId如果同时存在优先取tradeId
* @param {String} [opts.tradeId] 支付宝交易号,和商户订单号不能同时为空
* @param {String} [opts.outRequestId] 请求退款接口时,传入的退款请求号,如果在退款请求时未传入,则该值为创建交易时的外部交易号
* @param {String} [opts.appAuthToken] https://doc.open.alipay.com/doc2/detail.htm?treeId=216&articleId=105193&docType=1
*/
props.refundQuery = function (opts) {
var biz_content = {
out_trade_no: opts.outTradeId,
trade_no: opts.tradeId,
out_request_no: opts.outRequestId || opts.outTradeId
};
var params = this.makeParams('alipay.trade.fastpay.refund.query', biz_content);
if(this.appAuthToken) {
params.app_auth_token = this.appAuthToken;
}
var body = utl.processParams(params, this.rsaPrivate, this.signType);
return utl.request({
method: 'GET',
url: (this.sandbox? alipay_gate_way_sandbox : alipay_gate_way) + '?' + body
});
};
/**
* 查询对账单下载地址 https://doc.open.alipay.com/doc2/apiDetail.htm?spm=a219a.7629065.0.0.iX5mPA&apiId=1054&docType=4
* @param {Object} opts
* @param {String} [opts.billType] 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:
trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单;
* @param {String} [opts.billDate] 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。
* @param {String} [opts.appAuthToken] https://doc.open.alipay.com/doc2/detail.htm?treeId=216&articleId=105193&docType=1
*/
props.billDownloadUrlQuery = function (opts) {
var biz_content = {
bill_type: opts.billType,
bill_date: opts.billDate
};
var params = this.makeParams('alipay.data.dataservice.bill.downloadurl.query', biz_content);
if(this.appAuthToken) {
params.app_auth_token = this.appAuthToken;
}
var body = utl.processParams(params, this.rsaPrivate, this.signType);
return utl.request({
method: 'GET',
url: (this.sandbox? alipay_gate_way_sandbox : alipay_gate_way) + '?' + body
});
};
utl.js
var crypto = require('crypto');
var request = require('request');
var utl = module.exports = {};
Date.prototype.format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
/**
* 浅拷贝
* @param obj
* @returns {{}}
*/
utl.copy = function (obj) {
var ret = {};
for(var k in obj) {
ret[k] = obj[k];
}
return ret;
}
/**
* 对请求参数进行组装、编码、签名,返回已组装好签名的参数字符串
* @param {{Object} params 请求参数
* @param {String} privateKey 商户应用私钥
* @param {String} [signType] 签名类型 'RSA2' or 'RSA'
* @returns {String}
*/
utl.processParams = function (params, privateKey, signType) {
var ret = utl.encodeParams(params);
var sign = utl.sign(ret.unencode, privateKey, signType);
return ret.encode + '&sign=' + encodeURIComponent(sign);
};
/**
* 对请求参数进行组装、编码
* @param {Object} params 请求参数
* @returns {Object}
*/
utl.encodeParams = function (params) {
var keys = [];
for(var k in params) {
var v = params[k];
if (params[k] !== undefined && params[k] !== "") keys.push(k);
}
keys.sort();
var unencodeStr = "";
var encodeStr = "";
var len = keys.length;
for(var i = 0; i < len; ++i) {
var k = keys[i];
if(i !== 0) {
unencodeStr += '&';
encodeStr += '&';
}
unencodeStr += k + '=' + params[k];
encodeStr += k + '=' + encodeURIComponent(params[k]);
}
return {unencode:unencodeStr, encode:encodeStr};
};
/**
* 对字符串进行签名验证
* @param {String} str 要验证的参数字符串
* @param {String} sign 要验证的签名
* @param {String} publicKey 支付宝公钥
* @param {String} [signType] 签名类型
* @returns {Boolean}
*/
utl.signVerify = function (str, sign, publicKey, signType) {
var verify;
if(signType === 'RSA2') {
verify = crypto.createVerify('RSA-SHA256');
} else {
verify = crypto.createVerify('RSA-SHA1');
}
verify.update(str, 'utf8');
var result = verify.verify(publicKey, sign, 'base64');
return result;
};
/**
* 对字符串进行签名
* @param {String} str 要签名的字符串
* @param {String} privateKey 商户应用私钥
* @param {String} [signType] 签名类型
* @returns {String}
*/
utl.sign = function (str, privateKey, signType) {
var sha;
if(signType === 'RSA2') {
sha = crypto.createSign('RSA-SHA256');
} else {
sha = crypto.createSign('RSA-SHA1');
}
sha.update(str, 'utf8');
return sha.sign(privateKey, 'base64');
}
/**
* 发送请求 https://github.com/request/request
* @param {Object} opts 请求参数
* @param {String} opts.url 请求地址
* @param {String} opts.method GET|POST|PUT...
* @param {String} [opts.type] text/xml | application/json | application/x-www-form-urlencoded ...
* @param {Object} [opts.headers] {}
* @param {Object} [opts.qs] query参数
* @param {Buffer|String|ReadStream} [opts.body] 请求体
* @param {Object} [opts.form] form表单
* @returns {Promise.<Object>} resolve({response, body})
*/
utl.request = function(opts){
return new Promise(function(resolve, reject){
request(opts, function(err, res, body){
if(err){
reject(err);
return;
}
let ret = {response:res, body:body};
ret.ok = function() {
return res.statusCode === 200;
};
ret.json = function () {
if(res.body) return JSON.parse(res.body);
return null;
};
resolve(ret);
});
});
};
user.js
在这里你需要注意的是 appid需要填写你自己的appid
var express = require('express');
var router = express.Router();
var path = require('path');
var Alipay = require('./alipay');
var outTradeId = Date.now().toString();
var ali = new Alipay({
appId: '2016110100784951',
notifyUrl: 'http://127.0.0.1:3000/',
rsaPrivate: path.resolve('./routes/pem/app_private_key_nonjava.pem'),
rsaPublic: path.resolve('./routes/pem/alipay_public_key_nonjava.pem'),
sandbox: true,
signType: 'RSA2'
});
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index');
});
router.get('/pay', function(req, res, next) {
var url= ali.webPay({
body: "ttt",
subject: "ttt1",
outTradeId: "201503200101010230",
timeout: '90m',
amount: "1.0",
sellerId: '',
product_code: 'FAST_INSTANT_TRADE_PAY',
goods_type: "1",
return_url:"http://127.0.0.1:3000/",
})
var url_API = 'https://openapi.alipaydev.com/gateway.do?'+url;
res.json({url:url_API})
});
module.exports = router;
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/jquery.min.js"></script>
</head>
<script>
$(function () {
$("#btn").on("click", function () {
$.get('user/pay', function(data){
var url = data.url
console.log(url)
if(data){
window.location.href = url
console.log(url)
alert('成功')
}else{
alert('登录失败')
}
})
});
});
</script>
<body>
<p>welcome to</p>
<br/>
<button type="submit" id='btn'>付款</button>
</body>
</html>
app.js去运行,很多博客写的都没有交教小白如何运行
var user = require('./routes/user')
const ejs = require('ejs')
const express = require('express')
var bodyParser = require('body-parser')
var app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(express.static('static'))
//配置模板引擎
app.engine("html",ejs.__express)
app.set('view engine' ,'html')
app.use('/user',user)
app.listen(3000)
安装我的目录去写,一定是可以成功的。不懂的可以在下面评论,有空我会回答的。