PHP 微信支付
阿新 • • 發佈:2019-02-06
歡迎使用微信支付!
微信支付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();
}
}