1. 程式人生 > >PHP 微信支付

PHP 微信支付

歡迎使用微信支付!
微信支付API共四份(證書pkcs12格式、證書pem格式、證書金鑰pem格式、CA證書),為介面中強制要求時需攜帶的證書檔案。
證書屬於敏感資訊,請妥善保管不要洩露和被他人複製。
不同開發語言下的證書格式不同,以下為說明指引:
    證書pkcs12格式(apiclient_cert.p12)
        包含了私鑰資訊的證書檔案,為p12(pfx)格式,由微信支付簽發給您用來標識和界定您的身份
        部分安全性要求較高的API需要使用該證書來確認您的呼叫身份
        windows上可以直接雙擊匯入系統,匯入過程中會提示輸入證書密碼,證書密碼預設為您的商戶ID(如:10010000)
    證書pem格式(apiclient_cert.pem)


        從apiclient_cert.p12中匯出證書部分的檔案,為pem格式,請妥善保管不要洩漏和被他人複製
        部分開發語言和環境,不能直接使用p12檔案,而需要使用pem,所以為了方便您使用,已為您直接提供
        您也可以使用openssl命令來自己匯出:openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem
    證書金鑰pem格式(apiclient_key.pem)
        從apiclient_cert.p12中匯出金鑰部分的檔案,為pem格式
        部分開發語言和環境,不能直接使用p12檔案,而需要使用pem,所以為了方便您使用,已為您直接提供
        您也可以使用openssl命令來自己匯出:openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem
    CA證書(rootca.pem)

        微信支付api伺服器上也部署了證明微信支付身份的伺服器證書,您在使用api進行呼叫時也需要驗證所呼叫伺服器及域名的真實性
        該檔案為簽署微信支付證書的權威機構的根證書,可以用來驗證微信支付伺服器證書的真實性
        某些環境和工具已經內建了若干權威機構的根證書,無需引用該證書也可以正常進行驗證,這裡提供給您在未內建所必須根證書的環境中載入使用

商戶接入微信支付,呼叫API必須遵循以下規則:

傳輸方式 為保證交易安全性,採用HTTPS傳輸
提交方式 採用POST方法提交
資料格式 提交和返回資料都為XML格式,根節點名為xml
字元編碼 統一採用UTF-8字元編碼
簽名演算法 MD5,後續會相容SHA1、SHA256、HMAC等。
簽名要求 請求和接收資料均需要校驗簽名,詳細方法請參考安全規範-簽名演算法
證書要求 呼叫申請退款、撤銷訂單介面需要商戶證書
判斷邏輯 先判斷協議欄位返回,再判斷業務返回,最後判斷交易狀態

請求引數

欄位名 變數名 必填 型別 示例值 描述
公眾賬號ID appid String(32) wxd678efh567hg6787 微信支付分配的公眾賬號ID(企業號corpid即為此appId)
商戶號 mch_id String(32) 1230000109 微信支付分配的商戶號
隨機字串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 隨機字串,長度要求在32位以內。推薦隨機數生成演算法
簽名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 通過簽名演算法計算得出的簽名值,詳見簽名生成演算法
商品描述 body String(128) 騰訊充值中心-QQ會員充值

商品簡單描述,該欄位請按照規範傳遞,具體請見引數規定

商戶訂單號 out_trade_no String(32) 20150806125346 商戶系統內部訂單號,要求32個字元內,只能是數字、大小寫字母_-|* 且在同一個商戶號下唯一。詳見商戶訂單號
標價金額 total_fee Int 88 訂單總金額,單位為分,詳見支付金額
終端IP spbill_create_ip String(16) 123.12.12.123 APP和網頁支付提交使用者端ip,Native支付填呼叫微信支付API的機器IP。
通知地址 notify_url String(256) http://www.weixin.qq.com/wxpay/pay.php 非同步接收微信支付結果通知的回撥地址,通知url必須為外網可訪問的url,不能攜帶引數。
交易型別 trade_type String(16) JSAPI

JSAPI 公眾號支付

NATIVE 掃碼支付

APP APP支付

說明詳見引數規定

使用者標識 openid String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI時(即公眾號支付),此引數必傳,此引數為微信使用者在商戶對應appid下的唯一標識。openid如何獲取,可參考【獲取openid】。企業號請使用【企業號OAuth2.0介面】獲取企業號內成員userid,再呼叫【企業號userid轉openid介面】進行轉換

以下程式碼只是對微信支付進行了簡單分析,程式碼不可直接拿來使用,只做參考 ,這個是公眾號支付,trade_type=JSAPI時(即公眾號支付),此引數openid必傳

     /*發起微信公眾號支付訂單*/
     private function SubmitWxpaysend($subject,$OpenId,$OrderId,$PayMoney,$NotifyPage)          
     {
            //時間設定
            ini_set('date.timezone','Asia/Shanghai');
            //引入微信支付類檔案
            require_once  "WxPay.Api.php";
            require_once  "WxPay.JsApiPay.php";
            //統一下單
            $input = new \WxPayUnifiedOrder();
            $input->SetBody("商品描述");
            $input->SetAttach("附加資料");
            $input->SetOut_trade_no($OrderId); //商戶訂單號
            $input->SetTotal_fee($PayMoney);  //訂單總金額,單位為分
            $input->SetTime_start(date("YmdHis")); //訂單生成時間,格式為yyyyMMddHHmmss
            $input->SetTime_expire(date("YmdHis", time() + 600)); //訂單失效時間
            $input->SetGoods_tag('餘額充值'); //訂單優惠標記
            $input->SetNotify_url("weixin.com/web/admin/api/pay/".$NotifyPage); //設定回撥地址
            $input->SetTrade_type("JSAPI"); //交易型別 JSAPI 公眾號支付
            $input->SetOpenid($openId); //此引數為微信使用者在商戶對應appid下的唯一標識
            $input->SetAppid(WxPayConfig::APPID);//公眾賬號ID
            $input->SetMch_id(WxPayConfig::MCHID);//商戶號
            $input->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//終端ip	  	    
            $input->SetNonce_str(self::getNonceStr());//隨機字串
            $input->SetSign();//簽名
            //以上的方法是將值儲存到$values這個屬性的數組裡面
            //陣列轉換為XML
            $xml = $input->ToXml();//(這個方法是去拿$values的值,並且轉換為XML返回)

            //向統一下單介面發起請求
            $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//統一下單
            $response = self::postXmlCurl($xml, $url, false,$timeOut=6);
            //將xml轉為array
            $result = WxPayResults::Init($response);
            $tools = new \JsApiPay();
            //獲取jsapi支付的引數
            $jsApiParameters =$tools->GetJsApiParameters($order);
            return $jsApiParameters;

  }
 

     //隨機字串函式
     public static function getNonceStr($length = 32) 
     {
	     $chars = "abcdefghijklmnopqrstuvwxyz0123456789";  
	     $str ="";
           for ( $i = 0; $i < $length; $i++ ) {  
		    $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);  
		  } 
		 return $str;
	}

            //簽名生成函式
            public function SetSign()
            {
		      //簽名步驟一:按字典序排序引數
		       ksort($this->values);	
               //格式化引數格式化成url引數
		        $buff = "";
		      foreach ($this->values as $k => $v)
		         {
			        if($k != "sign" && $v != "" && !is_array($v)){
				      $buff .= $k . "=" . $v . "&";
			         }
		        }
		        $buff = trim($buff,"&");	
		        $string = $buff;
		        //簽名步驟二:在string後加入KEY
		        $string = $string . "&key=".WxPayConfig::KEY;
		        //簽名步驟三:MD5加密
		        $string = md5($string);
		        //簽名步驟四:所有字元轉為大寫
		        $result = strtoupper($string);
		        $this->values['sign'] = $result;
		        return $sign;
	         }

          
    //陣列轉XML
	public function ToXml()
	{
		if(!is_array($this->values)||count($this->values) <= 0){
    		throw new WxPayException("陣列資料異常!");
    	    } 	
    	  $xml = "<xml>";
    	  foreach ($this->values as $key=>$val){
    		 if (is_numeric($val)){
    			 $xml.="<".$key.">".$val."</".$key.">";
    		 }else{
    			 $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
    		 }
         }
          $xml.="</xml>";
          return $xml; 
   }  
 
	/**
	 * 以post方式提交xml到對應的介面url
	 * 
	 * @param string $xml  需要post的xml資料
	 * @param string $url  url
	 * @param bool $useCert 是否需要證書,預設不需要
	 * @param int $second   url執行超時時間,預設30s
	 * @throws WxPayException
	 */
     private static function postXmlCurl($xml, $url, $useCert = false, $second = 30)
	   {		
		$ch = curl_init();
		//設定超時
		curl_setopt($ch, CURLOPT_TIMEOUT, $second);
		
		//如果有配置代理這裡就設定代理
		if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0" 
			&& WxPayConfig::CURL_PROXY_PORT != 0){
			curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
			curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
		}
		curl_setopt($ch,CURLOPT_URL, $url);
		curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
		curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴格校驗
		//設定header
		curl_setopt($ch, CURLOPT_HEADER, FALSE);
		//要求結果為字串且輸出到螢幕上
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
	
		if($useCert == true){
			//設定證書
			//使用證書:cert 與 key 分別屬於兩個.pem檔案
			$certpath="cert/apiclient_cert.pem"; //證書路徑
			$certkeypath="cert/apiclient_key.pem"; //證書路徑
			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLCERT, $certpath);
			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLKEY, $certkeypath);
		}
		//post提交方式
		curl_setopt($ch, CURLOPT_POST, TRUE);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
		//執行curl
		$data = curl_exec($ch);
		api_functions::writeLog("postXmlCurl:".$data);
		//返回結果
		if($data){
			curl_close($ch);
			return $data;
		} else { 
			$error = curl_errno($ch);
			curl_close($ch);
			throw new WxPayException("curl出錯,錯誤碼:$error");
		}
	}

     /**
     * 將xml轉為array
     * @param string $xml
     * @throws WxPayException
     */
	public static function Init($xml)
	{	
		$obj = new self();
		$obj->FromXml($xml);
		//fix bug 2015-06-29
		if($obj->values['return_code'] != 'SUCCESS'){
			 return $obj->GetValues();
		}
		 $obj->CheckSign();
         return $obj->GetValues();
	  }
   }

	/**
	 * 
	 * 獲取jsapi支付的引數
	 * @param array $UnifiedOrderResult 統一支付介面返回的資料
	 * @throws WxPayException
	 * 
	 * @return json資料,可直接填入js函式作為引數
	 */
	public function GetJsApiParameters($UnifiedOrderResult)
	{
		if(!array_key_exists("appid", $UnifiedOrderResult)
		|| !array_key_exists("prepay_id", $UnifiedOrderResult)
		|| $UnifiedOrderResult['prepay_id'] == "")
		{
			throw new WxPayException("引數錯誤");
		}
		$jsapi = new WxPayJsApiPay();
		$jsapi->SetAppid($UnifiedOrderResult["appid"]);
		$timeStamp = time();
		$jsapi->SetTimeStamp("$timeStamp");
		$jsapi->SetNonceStr(WxPayApi::getNonceStr());
		$jsapi->SetPackage("prepay_id=" . $UnifiedOrderResult['prepay_id']);
		$jsapi->SetSignType("MD5");
		$jsapi->SetPaySign($jsapi->MakeSign());
		$parameters = json_encode($jsapi->GetValues());
		return $parameters;
	}    

下面對上面程式碼做過總結,這裡是app支付

    /*發起APP微信支付訂單*/
    private function SubmitWxpaysend($body,$subject,$UserId,$OrderId,$PayMoney,$PayType,$NotifyPage)
    {
        try
        {
            $appid=$this->wx_appid;
            $mch_id=$this->wx_mch_id;
            $wxKEY=$this->wx_wxKEY;
    
            header("Content-type: text/html; charset=utf-8");
    
            $order = $OrderId;
            $noceStr = md5(rand(100,1000).time());//獲取隨機字串
            $time = time();
            $paramarr = array(
                "appid"       =>    $appid,//公眾賬號ID
                "body"        =>    $subject,//商品描述
                "mch_id"      =>    $mch_id, //商戶號
                "nonce_str"   =>    $noceStr,//隨機字串
                "notify_url"  =>    "/web/admin/api/web/".$NotifyPage,//回撥地址
                "out_trade_no"=>    $order, //商戶訂單號
                "spbill_create_ip"=>$_SERVER["REMOTE_ADDR"],//APP和網頁支付提交使用者端ip
                "total_fee"   =>    ($PayMoney)*100,//訂單總金額,單位為分
                "trade_type"  =>    "APP"//APP APP支付 
            );
            //字串拼接,生成簽名
            ksort($paramarr);	
            $sign = "";
            foreach($paramarr as $k => $v){
                $sign .= $k."=".$v."&";
             }

            $sign .= "key=".$wxKEY;
            $sign = strtoupper(md5($sign));
            $paramarr['sign'] = $sign;
            //陣列轉XML
            $paramXml = "<xml>";
            foreach($paramarr as $k => $v){
                $paramXml .= "<" . $k . ">" . $v . "</" . $k . ">";
            }
            $paramXml .= "</xml>";
    
    
            $ch = curl_init ();
            @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳過證書檢查
            @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  // 從證書中檢查SSL加密演算法是否存在
            @curl_setopt($ch, CURLOPT_URL, "https://api.mch.weixin.qq.com/pay/unifiedorder");
            @curl_setopt($ch, CURLOPT_HEADER, FALSE);
            @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            @curl_setopt($ch, CURLOPT_POST, 1);
            @curl_setopt($ch, CURLOPT_POSTFIELDS, $paramXml);
            @$resultXmlStr = curl_exec($ch);
           
            curl_close($ch);

            //XML轉陣列
            $msg = array();
            $postStr =$resultXmlStr;
            $msg = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);    
            $time2 = time();
            $prepayid = $result['prepay_id'];
            $sign = "";
            $noceStr = md5(rand(100,1000).time());//獲取隨機字串
            $paramarr2 = array(
                "appid"     =>  $appid,
                "noncestr"  =>  $noceStr,
                "package"   =>  "Sign=WXPay",
                "partnerid" =>  $mch_id,
                "prepayid"  =>  $prepayid,
                "timestamp" =>  $time2
            );
             //字串拼接,生成簽名
                ksort($paramarr2);	
                $sign = "";
                foreach($paramarr2 as $k => $v){
                $sign .= $k."=".$v."&";
                }
                $sign .= "key=".$wxKEY;
                $sign = strtoupper(md5($sign));
                echo json_encode($paramarr2);
    
        }
        catch (Exception $ex)
        {
            echo $ex->getMessage();
        }
    }