首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 微信支付 JSAPI 完整封裝類(含統一下單、回調驗簽、工具方法)

     編輯:PHP 綜合知識

微信支付 JSAPI 接入是 PHP 開發高頻需求。本文提供一個開箱即用的 WxpayService 類,涵蓋統一下單、前端喚起參數生成、異步回調驗證、XML/簽名工具、HTTP 請求封裝等核心功能,代碼附詳細註釋,適合直接集成到項目中。

核心功能清單

  • createJsBizPackage():統一下單 → 返回前端 wx.chooseWXPay 所需完整參數包
  • notify():回調驗簽、業務邏輯預留、自動應答微信
  • 工具方法:curlGet/Post、createNonceStr、arrayToXml、getSign(MD5 + ASCII 排序)

完整類代碼

mchid = $mchid; // 商戶號
        $this->appid = $appid; // 公眾號 AppID
        $this->key   = $key;   // API 密鑰(商戶平台設置)
    }

    /**
     * 統一下單並生成 JSAPI 喚起參數
     * @param string $openid      用戶 OpenID
     * @param float  $totalFee    金額(元)
     * @param string $outTradeNo  商戶訂單號
     * @param string $orderName   商品描述
     * @param string $notifyUrl   異步回調地址(無問號)
     * @param string $timestamp   時間戳
     * @return array              含 appId, timeStamp, nonceStr, package, signType, paySign
     */
    public function createJsBizPackage($openid, $totalFee, $outTradeNo, $orderName, $notifyUrl, $timestamp) {
        $config = [
            'mch_id' => $this->mchid,
            'appid'  => $this->appid,
            'key'    => $this->key,
        ];

        $unified = [
            'appid'            => $config['appid'],
            'attach'           => '支付',           // 附加數據
            'body'             => $orderName,
            'mch_id'           => $config['mch_id'],
            'nonce_str'        => self::createNonceStr(),
            'notify_url'       => $notifyUrl,
            'openid'           => $openid,
            'out_trade_no'     => $outTradeNo,
            'spbill_create_ip' => '127.0.0.1',      // 建議改為真實客戶端 IP
            'total_fee'        => intval($totalFee * 100), // 元轉分
            'trade_type'       => 'JSAPI',
        ];

        $unified['sign'] = self::getSign($unified, $config['key']);
        $responseXml = self::curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder', self::arrayToXml($unified));

        $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
        if ($unifiedOrder === false) die('parse xml error');
        if ($unifiedOrder->return_code != 'SUCCESS') die($unifiedOrder->return_msg);
        if ($unifiedOrder->result_code != 'SUCCESS') die($unifiedOrder->err_code);

        $arr = [
            "appId"     => $config['appid'],
            "timeStamp" => $timestamp,
            "nonceStr"  => self::createNonceStr(),
            "package"   => "prepay_id=" . $unifiedOrder->prepay_id,
            "signType"  => 'MD5',
        ];
        $arr['paySign'] = self::getSign($arr, $config['key']);
        return $arr;
    }

    /**
     * 異步回調入口
     * @return SimpleXMLElement|false  解析後的通知對象
     */
    public function notify() {
        $config = [
            'mch_id' => $this->mchid,
            'appid'  => $this->appid,
            'key'    => $this->key,
        ];

        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"]; // PHP 7+ 建議用 file_get_contents('php://input')
        $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
        if ($postObj === false) die('parse xml error');
        if ($postObj->return_code != 'SUCCESS') die($postObj->return_msg);
        if ($postObj->result_code != 'SUCCESS') die($postObj->err_code);

        $arr = (array)$postObj;
        unset($arr['sign']);
        if (self::getSign($arr, $config['key']) == $postObj->sign) {
            // TODO: 這裡寫你的業務邏輯(如更新訂單狀態)
            // $out_trade_no = $postObj->out_trade_no;
            // $total_fee    = $postObj->total_fee;
            echo '';
            return $postObj;
        }
        return false;
    }

    /* ============ 工具方法 ============ */
    public static function curlGet($url, $options = []) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        if (!empty($options)) curl_setopt_array($ch, $options);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    public static function curlPost($url, $postData, $options = []) {
        if (is_array($postData)) $postData = http_build_query($postData);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        if (!empty($options)) curl_setopt_array($ch, $options);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    public static function createNonceStr($length = 16) {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    public static function arrayToXml($arr) {
        $xml = "";
        foreach ($arr as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<$key>$val";
            } else {
                $xml .= "<$key>";
            }
        }
        $xml .= "";
        return $xml;
    }

    /**
     * 簽名算法:ASCII 排序 → key=value 拼接 → 末尾加 &key=商戶密鑰 → MD5 → 大寫
     */
    public static function getSign($params, $key) {
        ksort($params, SORT_STRING);
        $str = self::formatQueryParaMap($params, false);
        return strtoupper(md5($str . "&key=" . $key));
    }

    protected static function formatQueryParaMap($paraMap, $urlEncode = false) {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if (null != $v && "null" != $v) {
                if ($urlEncode) $v = urlencode($v);
                $buff .= $k . "=" . $v . "&";
            }
        }
        return $buff ? substr($buff, 0, -1) : '';
    }
}

前端調用示例

// 後端返回的 payPackage 直接傳給 wx.chooseWXPay
wx.chooseWXPay({
    timestamp: payPackage.timeStamp,
    nonceStr: payPackage.nonceStr,
    package: payPackage.package,
    signType: payPackage.signType,
    paySign: payPackage.paySign,
    success: function(res) { alert('支付成功'); },
    fail: function(res) { alert('支付失敗:' + res.errMsg); }
});

生產環境避坑指南

  1. HTTPS 回調域名:notify_url 必須是外網可訪問的 HTTPS 域名,且目錄需在商戶平台配置的授權目錄下。
  2. 金額單位:統一下單 total_fee 為分,前端顯示為元,切記 ×100 / ÷100。
  3. 幂等性:回調可能重複觸發,業務邏輯需判斷訂單狀態防止重複入賬。
  4. 證書驗證:生產環境建議開啟 CURLOPT_SSL_VERIFYPEER 並配置 CA 證書,避免中間人攻擊。
  5. PHP 7+ 兼容:$GLOBALS['HTTP_RAW_POST_DATA'] 已棄用,推薦 file_get_contents('php://input')。

這個類已在多個項目中穩定運行,歡迎 Fork 並根據業務擴展(如退款、查訂單、紅包等接口)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved