1. 程式人生 > >node.js 支付寶完整支付、網頁支付、當面付款、訂單查詢、簽名校驗

node.js 支付寶完整支付、網頁支付、當面付款、訂單查詢、簽名校驗

網頁支付,我們知道是直接使用URl進行調整支付

https://openapi.alipay.com/gateway.do

然後後面帶什麼引數呢?
這是本地:
這裡寫圖片描述
然後ajax請求得到引數然後跳轉

router.get('/pay', function(req, res, next) {
    var url=  ali.webPay({
        body: "ttt",
        subject: "ttt1",
        outTradeId: "201503200101010222",
        timeout: '90m',
        amount: "0.1",
        sellerId: ''
, product_code: 'FAST_INSTANT_TRADE_PAY', goods_type: "1", return_url:"127.0.0.1:300", }) var url_API = 'https://openapi.alipay.com/gateway.do?'+url; res.json({url:url_API}) });

這裡寫圖片描述

思路就是
首先得到簽名
簽名需要這兩個檔案
這裡寫圖片描述
接下來是方法了

首先是封裝好的兩個js
alipay.js

/**
 * Created by ference on 2017/4/8.
 */
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

/**
 * Created by ference on 2017/4/8.
 */

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);
        });
    });
};

然後是調取

var express = require('express');
var router = express.Router();
var path = require('path');
var Alipay = require('../lib/alipay');
var utl = require('../lib/utl');

var outTradeId = Date.now().toString();



var ali = new Alipay({
    appId: '2017060207410259',
    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: "201503200101010222",
        timeout: '90m',
        amount: "0.1",
        sellerId: '',
        product_code: 'FAST_INSTANT_TRADE_PAY',
        goods_type: "1",
        return_url:"127.0.0.1:300",
    })

    var url_API = 'https://openapi.alipay.com/gateway.do?'+url;
    res.json({url:url_API})
});

module.exports = router;