PHP JSAPI調(diào)支付API實(shí)現(xiàn)微信支付功能詳解
一、首先我們來(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大寫)
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)為大寫
$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 是否需要證書,默認(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è)置證書
// 使用證書:cert 與 key 分別屬于兩個(gè).pem文件
// 證書文件請(qǐng)放入服務(wù)器的非web目錄下
$sslCertPath = 'sslCertPath'; // 證書路徑
$sslKeyPath = 'sslKeyPath'; // 證書路徑
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è)要自己寫(0.0)

MakeSign: 這里面的key要記得替換成自己真實(shí)的參數(shù)
ToXml
postXmlCurl : 注意這里面的證書要改成自己真實(shí)的哈

FromXml
到此這篇關(guān)于PHP JSAPI調(diào)支付API實(shí)現(xiàn)微信支付功能詳解的文章就介紹到這了,更多相關(guān)PHP微信支付內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
php安全攻防世界unserialize函數(shù)反序列化示例詳解
這篇文章主要介紹了php的安全防護(hù),關(guān)于攻防世界Web php unserialize正則表達(dá)式反序列化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10
php + ajax 實(shí)現(xiàn)的寫入數(shù)據(jù)庫(kù)操作簡(jiǎn)單示例
這篇文章主要介紹了php + ajax 實(shí)現(xiàn)的寫入數(shù)據(jù)庫(kù)操作,結(jié)合實(shí)例形式分析了php + ajax 寫入數(shù)據(jù)庫(kù)基本原理、操作技巧與相關(guān)使用注意事項(xiàng),需要的朋友可以參考下2020-05-05
php簡(jiǎn)單計(jì)算頁(yè)面加載時(shí)間的方法
這篇文章主要介紹了php簡(jiǎn)單計(jì)算頁(yè)面加載時(shí)間的方法,涉及php針對(duì)頁(yè)面加載時(shí)間的計(jì)算技巧,需要的朋友可以參考下2015-06-06
ThinkPHP基于think-queue的隊(duì)列插件實(shí)現(xiàn)消息推送
think-queue是ThinkPHP官方提供的一個(gè)消息隊(duì)列服務(wù),是專門支持隊(duì)列服務(wù)的擴(kuò)展包。think-queue消息隊(duì)列適用于大并發(fā)或返回結(jié)果時(shí)間比較長(zhǎng)且需要批量操作的第三方接口,可用于短信發(fā)送、郵件發(fā)送、APP推送。2022-12-12
PHP+iFrame實(shí)現(xiàn)頁(yè)面無(wú)需刷新的異步文件上傳
這篇文章主要介紹了PHP+iFrame實(shí)現(xiàn)頁(yè)面無(wú)需刷新的異步文件上傳,包含了iframe框架與form表單的運(yùn)用及PHP文件上傳等技巧,需要的朋友可以參考下2014-09-09
CKEditor4結(jié)合php實(shí)現(xiàn)上傳圖片功能
ckedit4是沒(méi)有圖片上傳功能的,單我們可以通過(guò)配置?config.js?文件來(lái)設(shè)置圖片上傳的接口,然后結(jié)合后端程序?qū)崿F(xiàn)圖片上傳,本文講解CKEditor4結(jié)合php實(shí)現(xiàn)上傳圖片功能的方法2024-03-03

