React網頁應用調起支付寶沙箱測試實現

龐順龍發表於2019-05-10

React+Redux網頁應用調起支付寶沙箱測試實現

1、新建React+Redux專案,建立空頁面,增加一個支付連結,編寫Redux方法呼叫Api生成支付連結   

  render() {
    let searchParam = {
      loginState: this.props.loginFailed,
      schcode: this.props.schcode,
      stucode: this.props.stucode
    }
    return (
      <div>
        <Profile name={this.props.username} image={this.props.photo} />
        <div id='testreducer' style={{ color: 'red', textAlign: 'center' }}>{this.state.helloValue}</div>
        <div onClick={this.alipay.bind(this)} style={{ color: 'red', textAlign: 'center', border: '1px solid blue', marginTop: '100px' }}>
          點選跳轉支付寶支付
        </div>
      </div>
    );
  }
  alipay() {
    let obj = {}
    obj.total_amount = 100;
    obj.stucode = this.props.stucode;
    obj.schcode = this.props.schcode;
    obj.subject = '支付寶沙箱環境支付測試';
    this.props.getAlipay.call(this, obj);
  }

2、支付寶沙箱註冊應用和支付寶沙箱版本安裝

https://openhome.alipay.com/platform/appDaily.htm?tab=info 支付寶登入後檢視個人測試AppID


使用支付寶公鑰私鑰工具,生成公鑰:https://docs.open.alipay.com/291/105971/

在沙箱工具掃描二維碼安裝安卓版本支付寶沙箱測試版本

3、Api專案編寫支付寶調起相關程式碼邏輯

定義appid、閘道器等引數

//支付寶引數
const app_id = 'xxxxxx';
const return_url = 'http://172.16.117.43:3001';
const notify_url = 'http://172.16.117.43:8001/api/alipay/alipaynotify';
const gateway = 'https://openapi.alipaydev.com/gateway.do?';
宣告api方法alipaysign,post型別,用於接收React頁面的api請求,最終res.json(util.success(finalparams))返回支付寶支付連結
/**
 * 獲取支付寶支付地址帶參
 */
router.post('/alipaysign', function (req, res) {
    let total_amount = req.body.total_amount;
    let schcode = req.body.schcode;
    let stucode = req.body.stucode;
    let subject = req.body.subject;

    let out_trade_no = moment().format('YYYYMMDDHHmmss') + moment().millisecond(); // + '_' + schcode + '_' + stucode;
    console.log(out_trade_no)

    //生成系統訂單併入庫
    let addJson = {
        out_trade_no: out_trade_no,
        schcode: schcode,
        stucode: stucode
    };
    // alipay.addNewOrder(addJson,function(err,data){  ...  });

    let params = new Map();
    params.set('app_id', app_id);
    params.set('method', 'alipay.trade.wap.pay');
    params.set('charset', 'utf-8');
    params.set('sign_type', 'RSA2');
    params.set('timestamp', moment().format('YYYY-MM-DD HH:mm:ss'));
    params.set('version', '1.0');
    params.set('return_url', return_url);
    params.set('notify_url', notify_url);
    params.set('biz_content', buildBizContent(subject, out_trade_no, total_amount));
    params.set('sign', buildSign(params));
    let finalparams = gateway + Array.from(params).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
    console.log(finalparams)
    res.json(util.success(finalparams));
})

生成業務請求引數的集合

function buildBizContent(subject, outTradeNo, totalAmount) {
    let bizContent = {
        subject: subject,
        out_trade_no: outTradeNo,
        total_amount: totalAmount,
        product_code: 'QUICK_MSECURITY_PAY',
    };
    return JSON.stringify(bizContent);
}

根據引數構建簽名

function buildSign(paramsMap) {
    let paramsList = Array.from(paramsMap);
    paramsList.sort();
    let paramsString = paramsList.map(([k, v]) => `${k}=${v}`).join('&');
    let privateKey = fs.readFileSync(path.join(__dirname, 'alipay_private.pem'), 'utf8');
    let signType = paramsMap.get('sign_type');
    return signWithPrivateKey(signType, paramsString, privateKey);
}

通過私鑰給字串簽名

function signWithPrivateKey(signType, content, privateKey) {
    let sign;
    if (signType.toUpperCase() === 'RSA2') {
        sign = crypto.createSign("RSA-SHA256");
    } else if (signType.toUpperCase() === 'RSA') {
        sign = crypto.createSign("RSA-SHA1");
    } else {
        throw new Error('請傳入正確的簽名方式,signType:' + signType);
    }
    sign.update(content);
    return sign.sign(privateKey, 'base64');
}

上面的程式碼就是一個完整的生成合規的支付寶支付連結,需要注意要用支付寶工具生成公鑰私鑰檔案放在合適的位置。


4、測試支付

5、接收支付寶非同步通知

/**
 * 通過私鑰給字串簽名
 * @param signType      返回引數的簽名型別:RSA2或RSA
 * @param content       需要加密的字串
 * @param privateKey    私鑰
 * @returns {number | PromiseLike<ArrayBuffer>}
 * @private
 */
function signWithPrivateKey(signType, content, privateKey) {
    let sign;
    if (signType.toUpperCase() === 'RSA2') {
        sign = crypto.createSign("RSA-SHA256");
    } else if (signType.toUpperCase() === 'RSA') {
        sign = crypto.createSign("RSA-SHA1");
    } else {
        throw new Error('請傳入正確的簽名方式,signType:' + signType);
    }
    sign.update(content);
    return sign.sign(privateKey, 'base64');
}


/**
 * 驗證支付寶非同步通知的合法性
 * @param params  支付寶非同步通知結果的引數
 * @returns {*}
 */
function verifySign(params) {
    try {
        let sign = params['sign']; //簽名
        let signType = params['sign_type']; //簽名型別
        let paramsMap = new Map();
        for (let key in params) {
            paramsMap.set(key, params[key]);
        }
        let paramsList = Array.from(paramsMap);
        paramsList.sort();
        let paramsString = paramsList.map(([k, v]) => `${k}=${decodeURIComponent(v)}`).join('&');
        let publicKey = fs.readFileSync(path.join(__dirname, 'alipay_public.pem'), 'utf8');
        return verifyWithPublicKey(signType, sign, paramsString, publicKey);
    } catch (e) {
        console.error(e);
        return false;
    }
}

/**
 * 驗證簽名
 * @param signType      返回引數的簽名型別:RSA2或RSA
 * @param sign          返回引數的簽名
 * @param content       引數組成的待驗籤串
 * @param publicKey     支付寶公鑰
 * @returns {*}         是否驗證成功
 * @private
 */
function verifyWithPublicKey(signType, sign, content, publicKey) {
    try {
        let verify;
        if (signType.toUpperCase() === 'RSA2') {
            verify = crypto.createVerify('RSA-SHA256');
        } else if (signType.toUpperCase() === 'RSA') {
            verify = crypto.createVerify('RSA-SHA1');
        } else {
            throw new Error('未知signType:' + signType);
        }
        verify.update(content);
        return verify.verify(publicKey, sign, 'base64')
    } catch (err) {
        console.error(err);
        return false;
    }
}

/**
 * 獲取支付寶支付非同步通知
 */
router.post('/alipaynotify', function (req, res) {
    let signType = req.body.sign_type; //簽名型別:商戶生成簽名字串所使用的簽名演算法型別,目前支援RSA2和RSA,推薦使用RSA2
    let sign = req.body.sign; //簽名:請參考<a href="#yanqian" class="bi-link">非同步返回結果的驗籤</a>
    let tradeNo = req.body.trade_no; //支付寶交易號:支付寶交易憑證號
    let outTradeNo = req.body.out_trade_no; //商戶訂單號:原支付請求的商戶訂單號
    let buyerLogonId = req.body.buyer_logon_id; //買家支付寶賬號:買家支付寶賬號
    let sellerId = req.body.seller_id; //賣家支付寶使用者號:賣家支付寶使用者號 
    let tradeStatus = req.body.trade_status; //交易狀態:交易目前所處的狀態,見<a href="#jiaoyi" class="bi-link">交易狀態說明</a>
    let totalAmount = req.body.total_amount; //訂單金額:本次交易支付的訂單金額,單位為人民幣(元)
    let receiptAmount = req.body.receipt_amount; //實收金額:商家在交易中實際收到的款項,單位為元
    let gmtPayment = req.body.gmt_payment;//交易付款時間:該筆交易的買家付款時間。格式為yyyy-MM-dd HH:mm:ss

    let isSuccess = verifySign(req.body);
    console.log(isSuccess)
    if (isSuccess) {
        if (tradeStatus === 'TRADE_FINISHED') {
            //交易狀態TRADE_FINISHED的通知觸發條件是商戶簽約的產品不支援退款功能的前提下,買家付款成功;或者,商戶簽約的產品支援退款功能的前提下,交易已經成功並且已經超過可退款期限。

        } else if (tradeStatus === 'TRADE_SUCCESS') {
            //狀態TRADE_SUCCESS的通知觸發條件是商戶簽約的產品支援退款功能的前提下,買家付款成功

        } else if (tradeStatus === 'WAIT_BUYER_PAY') {
            //交易建立,等待買家付款

        } else if (tradeStatus === 'TRADE_CLOSED') {
            //未付款交易超時關閉,或支付完成後全額退款

        }
        res.send('success');
    } else {
        res.send('fail');
    }
});


請喊我大龍哥最後編輯於:7個月前

內容均為作者獨立觀點,不代表八零IT人立場,如涉及侵權,請及時告知。

相關文章