PHP JSAPI調(diào)支付API實(shí)現(xiàn)微信支付功能詳解
目錄
- 一、首先我們來(lái)填個(gè)坑
- 二、代碼示例
- 1.請(qǐng)求參數(shù)配置
- 2.統(tǒng)一下單API
- 3.MakeSign 簽名
- 4.ToXml 數(shù)組參數(shù)轉(zhuǎn)xml
- 5.postXmlCurl 發(fā)送請(qǐng)求
- 6.FromXml 結(jié)果xml參數(shù)轉(zhuǎn)數(shù)組
- 總結(jié)
一、首先我們來(lái)填個(gè)坑
支付驗(yàn)簽失敗
這個(gè)問(wèn)題折磨了我兩天,官方文檔比較含糊不清。各種百度下來(lái)的方法試過(guò)之后也不盡人意,最后發(fā)現(xiàn)問(wèn)題是沒(méi)有二次簽名
二次簽名需要參數(shù)(代碼會(huì)展示在哪里二次簽名):
appId: 商戶申請(qǐng)的公眾號(hào)對(duì)應(yīng)的appid(I大寫(xiě))
nonceStr: 隨機(jī)字符串(注意是JSAPI下單接口中返回的 nonce_str、不是重新生成)
package: 統(tǒng)一下單接口返回的prepay_id參數(shù)值 ,(注意格式prepay_id=wx.....)
signType: 簽名類型、(官方文檔)僅支持RSA。
(我的簽名類型是 HMAC-SHA256 也是可以的,必須和下單使用的簽名類型保持一致)
timeStamp:時(shí)間戳(這里要把 time() 轉(zhuǎn)成字符串類型)
注明:使用這五個(gè)參數(shù)生成的 paySign 簽名才是需要返給前端的(
)
官方文檔實(shí)例要計(jì)算簽名也給我整的蒙圈,最后發(fā)現(xiàn)直接將五個(gè)必須參數(shù)生成的簽名返給前端就可以直接調(diào)取API了
二、代碼示例
1.請(qǐng)求參數(shù)配置
$oInput = [ "body" => "測(cè)試商品", // 商品說(shuō)明 "attach" => "測(cè)試場(chǎng)景", // 自定義參數(shù):可以用來(lái)做回調(diào)后場(chǎng)景區(qū)分 "out_trade_no" => "測(cè)試單號(hào)" . time(), // 自定義訂單號(hào) "total_fee" => 1 * 100, // 付款金額:記得*100 微信官方是以分為單位 "goods_tag" => "",// 優(yōu)惠券相關(guān)參數(shù) "notify_url" => "http://...", // 回調(diào)通知地址 "trade_type" => "JSAPI", // 支付方式 "openid" => $openid, // 付款用戶openid // "profit_sharing" => "Y", // 是否分賬的標(biāo)識(shí) ]; $res = $this->unifiedOrder($oInput); // 這里我調(diào)用的統(tǒng)一下單 return $res; // 返給前端帶APPID等參數(shù)給前端去調(diào)用支付
2.統(tǒng)一下單API
public function unifiedOrder($inputObj, $timeOut = 6) { $url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 首次簽名參數(shù) $oValues = [ "body" => $inputObj["body"], // 設(shè)置商品或支付單簡(jiǎn)要描述 "attach" => $inputObj["attach"], // 設(shè)置附加數(shù)據(jù),用于商戶攜帶訂單的自定義數(shù)據(jù) "out_trade_no" => $inputObj["out_trade_no"], // 設(shè)置商戶系統(tǒng)內(nèi)部的訂單號(hào),transaction_id、out_trade_no二選一,如果同時(shí)存在優(yōu)先級(jí):transaction_id> out_trade_no "total_fee" => $inputObj["total_fee"], // 設(shè)置訂單總金額,只能為整數(shù),單位:分 "time_start" => date("YmdHis"), // 設(shè)置訂單生成時(shí)間 "time_expire" => date("YmdHis", time() + 600), // 設(shè)置訂單失效時(shí)間 "goods_tag" => $inputObj["goods_tag"], // 設(shè)置商品標(biāo)記,代金券或立減優(yōu)惠功能的參數(shù) "notify_url" => $inputObj["notify_url"], // 獲取接收微信支付異步通知回調(diào)地址的值 "trade_type" => $inputObj["trade_type"], // JSAPI,NATIVE,APP "openid" => $inputObj["openid"], // 用戶在商戶appid下的唯一標(biāo)識(shí) //"profit_sharing" => $inputObj["profit_sharing"], // 是否需要分賬 "appid" => "appid", // app_id:替換真實(shí)的 "mch_id" => "mchid", // 商戶號(hào):替換真實(shí)的 "spbill_create_ip" => $_SERVER["REMOTE_ADDR"], // 終端ip "nonce_str" => "自定義生成", // 隨機(jī)32位字符串 "sign_type" => "HMAC-SHA256", // 簽名類型,自行替換 ]; // 首次簽名 ksort($oValues); $oValues["sign"] = $this->MakeSign($oValues); // 調(diào)用簽名 $xml = $this->ToXml($oValues); // 數(shù)字轉(zhuǎn)xml類型 $response = self::postXmlCurl($xml, $url, false, $timeOut); // 請(qǐng)求 $result = $this->FromXml($response); // 請(qǐng)求結(jié)果從xml轉(zhuǎn)成數(shù)組類型// 二次簽名參數(shù) $oResult = [ "appId" => $result["appid"], // 首次請(qǐng)求中的appid "nonceStr" => $result["nonce_str"], // 首次請(qǐng)求中的nonce_str "package" => "prepay_id=" . $result["prepay_id"],// 首次請(qǐng)求中的prepay_id "signType" => "HMAC-SHA256", // 跟首次簽名中的簽名類型參數(shù)保持一致 "timeStamp" => (string)(time()),// 時(shí)間戳轉(zhuǎn)字符串類型 ];// 二次簽名 $oResult["paySign"] = $this->MakeSign($oResult); // 調(diào)用簽名 $result = json_encode($oResult); // encode數(shù)組 return $result; // 直接返回 }
3.MakeSign 簽名
/** * 生成簽名 * @param bool $needSignType 是否需要補(bǔ)signtype * @return 簽名,本函數(shù)不覆蓋sign成員變量,如要設(shè)置簽名需要調(diào)用SetSign方法賦值 */ public function MakeSign($values, $needSignType = true) { if ($needSignType) { $sSignType = "HMAC-SHA256"; // 可以在文檔開(kāi)頭用枚舉定義: 所有簽名類型必須一致 } $sKey = "key"; // 獲取支付參數(shù)key // 簽名步驟一:按字典序排序參數(shù) ksort($values); $string = $this->ToUrlParams($values); // 簽名步驟二:在string后加入KEY $string = $string . "&key=" . $sKey; // 簽名步驟三:MD5加密或者HMAC-SHA256 if ($sSignType == "MD5") { $string = md5($string); } else if ($sSignType == "HMAC-SHA256") { $string = hash_hmac("sha256", $string, $sKey); } else { return "簽名類型不支持!"; } // 簽名步驟四:所有字符轉(zhuǎn)為大寫(xiě) $result = strtoupper($string); return $result; }
4.ToXml 數(shù)組參數(shù)轉(zhuǎn)xml
public function ToXml($values) { if (!is_array($values) || count($values) <= 0) { return "數(shù)組數(shù)據(jù)異常!"; } $xml = "<xml>"; foreach ($values as $key => $val) { if (is_numeric($val)) { $xml .= "<" . $key . ">" . $val . "</" . $key . ">"; } else { $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">"; } } $xml .= "</xml>"; return $xml; }
5.postXmlCurl 發(fā)送請(qǐng)求
/** * 以post方式提交xml到對(duì)應(yīng)的接口url * * @param WxPayConfigInterface $config 配置對(duì)象 * @param string $xml 需要post的xml數(shù)據(jù) * @param string $url url * @param bool $useCert 是否需要證書(shū),默認(rèn)不需要 * @param int $second url執(zhí)行超時(shí)時(shí)間,默認(rèn)30s */ private function postXmlCurl($xml, $url, $useCert = false, $second = 30) { $ch = curl_init(); $curlVersion = curl_version(); $ua = "WXPaySDK/" . self::VERSION . " (" . PHP_OS . ") PHP/" . PHP_VERSION . " CURL/" . $curlVersion["version"] . " " . $aWxpayParam["mchid"]; //設(shè)置超時(shí) curl_setopt($ch, CURLOPT_TIMEOUT, $second); $proxyHost = "0.0.0.0"; $proxyPort = 0; // 如果有配置代理這里就設(shè)置代理 if ($proxyHost != "0.0.0.0" && $proxyPort != 0) { curl_setopt($ch, CURLOPT_PROXY, $proxyHost); curl_setopt($ch, CURLOPT_PROXYPORT, $proxyPort); } curl_setopt($ch, CURLOPT_URL, $url); // curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE); // curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴(yán)格校驗(yàn) curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //嚴(yán)格校驗(yàn) curl_setopt($ch, CURLOPT_USERAGENT, $ua); // 設(shè)置header curl_setopt($ch, CURLOPT_HEADER, FALSE); // 要求結(jié)果為字符串且輸出到屏幕上 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); if ($useCert == true) { // 設(shè)置證書(shū) // 使用證書(shū):cert 與 key 分別屬于兩個(gè).pem文件 // 證書(shū)文件請(qǐng)放入服務(wù)器的非web目錄下 $sslCertPath = "sslCertPath";// 證書(shū)路徑 $sslKeyPath = "sslKeyPath"; // 證書(shū)路徑 curl_setopt($ch, CURLOPT_SSLCERTTYPE, "PEM"); curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath); curl_setopt($ch, CURLOPT_SSLKEYTYPE, "PEM"); curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath); } // post提交方式 curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); // 運(yùn)行curl $data = curl_exec($ch); // 返回結(jié)果 if ($data) { curl_close($ch); return $data; } else { $error = curl_errno($ch); curl_close($ch); throw new WxPayException("curl出錯(cuò),錯(cuò)誤碼:$error"); } }
6.FromXml 結(jié)果xml參數(shù)轉(zhuǎn)數(shù)組
/** * 將xml轉(zhuǎn)為array * @param string $xml * @throws WxPayException */ public function FromXml($xml) { if (!$xml) { return "xml數(shù)據(jù)異常!"; } //將XML轉(zhuǎn)為array //禁止引用外部xml實(shí)體 libxml_disable_entity_loader(true); $res = json_decode(json_encode(simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA)), true); return $res; }
總結(jié)
注意統(tǒng)一下單中五個(gè)調(diào)用方法別忘了:
getNonceStr:我沒(méi)貼出來(lái),這個(gè)要自己寫(xiě)(0.0)
MakeSign: 這里面的key要記得替換成自己真實(shí)的參數(shù)
ToXml
postXmlCurl : 注意這里面的證書(shū)要改成自己真實(shí)的哈
FromXml
到此這篇關(guān)于PHP JSAPI調(diào)支付API實(shí)現(xiàn)微信支付功能詳解的文章就介紹到這了,更多相關(guān)PHP微信支付內(nèi)容請(qǐng)搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!
