NodeJS 支付寶網站支付 Demo 開發

開發介紹

由於後期的一些需求須要使用到支付寶網站支付業務,而近期又學習了 NodeJS 後端的開發,因而乎從網上找了一些資料,而支付寶開放平臺又沒有現成的 Demo 案例,也只有 NodeJS 開發的 SDK 因此,本身花了一些時間嘗試使用 NodeJS 開發一個示例 Demo 便於後面開發項目時去使用。 測試 DEMO:github.com/xiluotop/No…javascript

開發步驟

  • 一個訂單的建立到支付成功的文字過程:

    前端頁面 -> 向服務端發送訂單信息 -> 服務端確認信息向客戶端發送確認信息 -> 客戶端確認信息向服務端發送訂單請求 -> 服務端驗證訂單請求信息 -> 服務端像支付寶發送訂單生成 -> 支付寶向服務端返回訂單數據 -> 服務端向客戶端發送支付寶的表單信息 -> 客戶端跳轉到支付寶支付頁面 -> 客戶端支付成功 -> 支付寶讓客戶端同步跳轉到服務端指定的頁面 -> 支付寶異步通知服務端訂單支付結果 -> 服務端接收異步通知作相應的業務處理。html

  • 支付寶調用支付接口的常規流程:
    15c9f13607750c190a220fe7f4d9debd.png

準備工做

  • 安裝 alipay 的 SDK工具:www.npmjs.com/package/ali… npm install alipay-sdk
  • 獲取 alipay 的應用相關信息
    • appid
    • 祕鑰
      • 應用私鑰
      • 應用公鑰
      • 支付寶公鑰(從支付寶開放平臺上進行祕鑰查詢那裏能夠獲取)
    • 網關
    • 這幾步的獲取方式可查看https://jiangck.com/articles/2019/08/10/1565412139037.html
  • 準備工做後的目錄應以下:
    image.png

開發工做

  • 配置 SDK
    • 建立一個 alipay_config.js 此 js 腳本用於設置 SDK 的相關信息
    const fs = require('fs');
    const path = require('path');
    
    // 這裏配置基本信息
    const AlipayBaseConfig = {
        appId: '', // 應用 ID
        privateKey: fs.readFileSync(path.join(__dirname, './sandbox-pem/private_pem2048.txt'), 'ascii'), // 應用私鑰
        alipayPublicKey: '',// 支付寶公鑰
        gateway: 'https://openapi.alipaydev.com/gateway.do', // 支付寶的應用網關,此時爲沙箱環境的網關
        charset:'utf-8',	// 字符集編碼
        version:'1.0',		// 版本,默認 1.0
        signType:'RSA2'		// 祕鑰的解碼版本
    };
    
    module.exports = {
        AlipayBaseConfig: AlipayBaseConfig,	// 將配置模塊暴露供初始化調用
    }
    複製代碼
  • 建立 createOrder.js 文件,封裝生成支付寶訂單的模塊
    • 導入 SDK 環境:const AlipaySDK = require("alipay-sdk").default;
    • 導入配置:const alipayConfig = require(path.join(__dirname, './alipay_config.js')); // 先前定義好的 alipay_config.js 配置模塊
    • 獲取 alipaySDK 的實例化對象並初始化:const alipay = new AlipaySDK(alipayConfig.AlipayBaseConfig)
    • 封裝 createOrder
      • 根據官方示例:www.yuque.com/chenqiu/ali… ,alipay.trade.page.pay(統一收單下單並支付頁面接口) 最後返回的是一個 html 片斷,因此須要使用 formData 進行數據的封裝請求
      • 引入 alipayFormData 構造函數,用來建立網站支付須要的 form 表單 const AlipayFormData = require('alipay-sdk/lib/form').default;
      • 封裝好的 createOrder 以及詳細代碼以下:
      // 編寫一個建立支付訂單的函數,異步等待執行的函數
      async function createOrder(goods) {
          let method = 'alipay.trade.page.pay'; // 統一收單下單並支付頁面接口
          // 公共參數 可根據業務須要決定是否傳入,當前不用
          // let params = {
          // app_id: '2016101000654289', // 應用 id
          // method: method, // 調用接口
          // format: 'JSON', // 返回數據
          // charset: 'utf-8', // 字符編碼
          // sign_type: 'RSA2', // 驗籤類型
          // timestamp: getFormatDate(), // 請求時間戳
          // version: '1.0', // 版本
          // }
          // 根據官方給的 API 文檔提供的一個參數集合
          let bizContent = {
              out_trade_no: Date.now(), // 根據時間戳來生成一個訂單號,
              product_code: 'FAST_INSTANT_TRADE_PAY', // 商品碼,當前只支持這個
              total_amount: goods.cost, // 商品價格
              subject: goods.goodsName, // 商品名稱
              timeout_express: '5m', // 超時時間
              passback_params: JSON.stringify(goods.pack_params), // 將會返回的一個參數,可用於自定義商品信息最後作通知使用
          }
          const formData = new AlipayFormData(); // 獲取一個實例化對象
          formData.addField('returnUrl', 'http://jiangck.com:9999/payresult'); // 客戶端支付成功後會同步跳回的地址
          formData.addField('notifyUrl', 'http://jiangck.com:9999/notify.html'); // 支付寶在用戶支付成功後會異步通知的回調地址,必須在公網 IP 上才能收到
          formData.addField('bizContent', bizContent); // 將必要的參數集合添加進 form 表單
      
          // 異步向支付寶發送生成訂單請求, 第二個參數爲公共參數,不須要的話傳入空對象就行
          const result = await alipay.exec(method, {}, {
              formData: formData
          });
          // 返回訂單的結果信息
          return result;
      }
      複製代碼
    • 將生成訂單的方法暴露出去
      module.exports = {
          createOrder: createOrder
      }
      複製代碼
  • 建立 checkSign.js 驗籤模塊
    • 驗籤的做用是在當用於支付成功後,支付寶異步向服務器設置好的回調地址發送 post 請求,用來告知服務器用戶的支付信息,並且服務器最終也是依據這個異步通知來處理邏輯業務,而不是靠支付寶的同步跳轉,而且當支付寶發送了異步通知請求後,服務端須要作驗證檢查,這一步是必須的,由於涉及金錢交易必需要嚴謹,必定要覈實請求過來的信息真實性。合法後才能作邏輯處理,封裝的驗籤功能也是基於 alipaySDK 的工具,只是用來方便調用,代碼以下:
    const path = require('path');
    
    // 用於通知驗籤
    // ------配置 alipay SDK 環境
    // 導入 SDK
    const AlipaySDK = require("alipay-sdk").default;
    // 導入配置
    const alipayConfig = require(path.join(__dirname, './alipay_config.js'));
    // 初始化
    const alipaySdk = new AlipaySDK(alipayConfig.AlipayBaseConfig);
    
    async function checkNotify(obj) {
        const result = await alipaySdk.checkNotifySign(obj);
        return result;
    }
    
    module.exports = checkNotify;
    複製代碼
  • 建立 mysql.js 模塊,用於對數據庫的存儲工做進行簡單的封裝
    • 代碼以下:
    /* 這個自定義模塊用來進行 mysql 數據庫訂單的增長和查詢功能 */
    
    // 引入 mysql 模塊
    const mysql = require('mysql');
    
    // 配置 mysql
    const mysqlConfig = {
        host: 'localhost', // 數據庫主機名
        port: '3306', // 端口號
        user: 'root', // 用戶名
        password: '123456', // 密碼
        database: 'alipay', // 數據庫名
    }
    
    // 封裝查詢函數
    function selectSql(sqlstr,callback) {
        // 創建數據庫鏈接
        let sql = mysql.createConnection(mysqlConfig);
        let result = null;
        if (sql) {
            sql.query(sqlstr, callback);
            // 關閉數據庫鏈接
            sql.end();
        }
    }
    
    // 封裝添加函數
    function addSql(sqlstr,callback) {
        return selectSql(sqlstr,callback);
    }
    
    // 將兩個數據庫操做方法暴露
    module.exports = {
        selectSql: selectSql,
        addSql: addSql
    }
    複製代碼
  • 建立 server.js 用來創建一個 http 服務器,而且作好各接口的請求處理 代碼以下:
    const path = require("path");
    const bp = require('body-parser');
    // 引入自定義 mysql 工具
    const mysql = require(path.join(__dirname, './mysql.js'));
    // 引入 express
    const express = require('express');
    // 獲取 express 實例對象
    let app = express();
    
    // 設置託管靜態資源
    app.use(express.static(path.join(__dirname, './public')));
    // 處理 post 請求參數
    app.use(bp.urlencoded({
        extended: false
    }));
    
    // 前端響應要建立訂單的數據對象
    app.get('/payinfo', (req, res) => {
        let data = req.query;
        // 作一個簡單的商品判斷
        if (data && (data.goodsName === '大衛龍' || data.goodsName === '冰闊咯' || data.goodsName === '雪碧' || data.goodsName === 'QQB') && data.count && data.cost) {
            res.send(Object.assign(data, {
                code: 200,
            }));
        } else {
            res.setHeader('content-type', 'application/javascript');
            res.send('alert("信息有誤,請從新嘗試!!!")');
        }
    })
    
    // 獲取建立訂單的自定義模塊
    const createOrder = require(path.join(__dirname, './createOrder.js')).createOrder;
    // 獲取驗籤自定義模塊
    const checkSign = require(path.join(__dirname, './checkSign.js'));
    
    // 生成訂單請求
    app.post('/createOrder', (req, res) => {
        console.log(req.body.price);
        req.body.pack_params = {
            payName: req.body.payName,
            goodsName: req.body.goodsName,
            price: req.body.price,
            count: req.body.count,
            cost: req.body.cost,
        }
        async function asyncCreate() {
            const result = await createOrder(req.body);
            res.send(result);
        }
        asyncCreate();
    });
    
    // 支付的信息展現
    app.get('/payresult', (req, res) => {
        let htmlStr = '';
        htmlStr += `<p>` + '商戶訂單號' + ': ' + req.query.out_trade_no + '</p>'
        htmlStr += `<p>` + '支付寶交易訂單號' + ': ' + req.query.trade_no + '</p>'
        htmlStr += `<p>` + '交易金額' + ': ' + req.query.total_amount + '¥</p>'
        htmlStr += `<p>` + '交易時間' + ': ' + req.query.timestamp + '¥</p>'
        htmlStr += '<h1 style:"text-align:center;">支付成功!!!<a href="./index.html">返回首頁!</a></h1>'
        res.send(htmlStr);
    })
    
    app.post('/notify.html', (req, res) => {
        // 輸出驗簽結果
        async function checkResult(postData) {
            let result = await checkSign(postData);
            if (result) {
                // console.log('訂單成功支付!!!請作處理')
                // console.log(req.body);
                let data = req.body;
                let goods = JSON.parse(data.passback_params);
                let sqlStr = ` insert into order_list value("${data.out_trade_no}", "${data.trade_no}", "${goods.goodsName}", ${goods.price}, ${goods.count}, ${data.total_amount}, "支付成功", "${goods.payName}"); `;
    	// 響應支付寶處理成功,不然支付寶會一直定時發送異步通知
                res.end('success');
                mysql.addSql(sqlStr)
            }
        }
        checkResult(req.body);
    })
    
    // 查詢訂單接口
    app.get('/getorder', (req, res) => {
        mysql.selectSql('select * from order_list', (err, result) => {
            result = Object.assign({
                code: 200,
                msg: '獲取成功',
                list: JSON.stringify(result),
            })
            res.send(result);
        });
    })
    
    app.listen(9999, () => {
        console.log('server start with 9999...');
    })
    複製代碼
  • public 靜態資源
    • 靜態資源只是前端用來讓客戶端進行操做的展現,具體文件能夠訪問後面的 github 開源 Demo 獲取,裏面含有一個 alipay.sql 數據庫的轉儲文件,也可自行建立。

總結

  • 以上步驟就是一個支付寶網站支付接口的調用 Demo 簡單開發。
  • 開發過程會遇到的問題有以下幾點:
    • SDK 配置中的公鑰配置,看清楚是填寫支付寶公鑰,而不是應用公鑰
    • 祕鑰算法必定要對應 RSA 與 RSA2 必定要弄清楚,是什麼加密就寫什麼
    • 驗籤失敗時要檢查加密算法支付寶公鑰應用私鑰/公鑰加密類型
    • 驗籤經過後必定要想支付響應成功信息的字符串 "success",否則會一直受到異步通知從而出現業務屢次處理。
  • 本測試 DEMO 已經開源 Github,可點擊我進行測試 Demo 的獲取。若是對您有幫助的還請 Star 下O(∩_∩)O。
  • 我也是剛進行學習的,這也是個人第一個支付測試 demo ,確定會有很 bug,後續會添加更多的接口測試 demo 和一步步的完善,有什麼問題的話能夠留言一塊兒交流喔。
相關文章
相關標籤/搜索