上一篇講到成功獲取 openid,本篇要調用微信統一接口創建預支付交易單
除付款碼支付場景以外,商戶系統先調用該接口在微信支付服務后台生成預支付交易單,返回正確的預支付交易會話標識后再按Native、JSAPI、APP等不同場景生成交易串調起支付。
第三步,調用微信統一下單接口創建預支付交易單
微信統一下單API是微信支付的一個“統一”處理入口,官方給出的地址是
https://api.mch.weixin.qq.com/pay/unifiedorder
https://api2.mch.weixin.qq.com/pay/unifiedorder(備用域名)
首先先按照 統一下單 接口一問中給的 請求參數 准備必要的參數,在本例(JSAPI)中至少需要參數如下
- appid,公眾號AppID
- mch_id,商戶號,一串10位數字
- device_info,設備號,非必填但有一定作用,PC網頁或公眾號內支付可以傳"WEB",本例中就設置為"WEB"
- nonce_str,隨機字符串,請求方提供的隨機信息,主要保證簽名不可預測
- sign,通過簽名算法計算得出的簽名值,詳見簽名算法
- sign_type,簽名類型,截止編輯時支持(默認)"MD5"和"HMAC-SHA256",用哪種算法進行的簽名要通過該字段表示
- body,商品描述,商戶按規范創建
- out_trade_no,商戶系統內部訂單號,按微信的規則生成
- total_fee,整型,單位分,訂單總金額
- spbill_create_ip,"用戶的客戶端IP",這個詞兒是微信給出的截止到編輯時從字面上我並不確定指的是請求服務器的服務器地址還是訪問商戶服務器的某個用戶"手機"的地址,但通過代碼應該是后者,詳見之后的代碼示例
- notify_url,異步接收微信支付結果通知的回調地址
- trade_type,交易類型的標識,JSAPI-JSAPI支付,NATIVE-Native支付,APP-APP支付,MWEB-H5支付
- openid,第一篇中來自微信用戶的openid值
按照 簽名算法 計算簽名
- 把所有要傳遞的參數鍵值對去掉值是空的,剩下的參數名ASCII碼從小到大排序后,使用URL鍵值對格式(key1=value1&key2=value2...)拼接成StringA
- 把StringA后面多加一組鍵值 &key=商戶平台密鑰 獲得StringB
- 把StringB按要求做"MD5"或"HMAC-SHA256"計算,並將結果字符轉為大寫
- 微信甚至還提供了一個在線校驗工具幫助開發者檢查生成的簽名是否正確,跳到工具。使用方法是選擇好 簽名類型,校驗方式選擇 XML (不是必須只是為了省事兒),XML源串 輸入不帶 sign 信息的部分如
<xml>
<appid>wxcxxxxx5bbf</appid>
<mch_id>15xxxxxxxxxxxx61</mch_id>
<device_info>WEB</device_info>
<nonce_str>6743482E92E61DE587B52D6C7DEBFE79</nonce_str>
<body>xx科技-測試</body>
<out_trade_no>TY-OUTTRADENO-TEST-1561440044</out_trade_no>
<total_fee>1</total_fee>
<spbill_create_ip>36.xx.xx.185</spbill_create_ip>
<notify_url>http://wxpay.txxxt.com/jsapi/unifiedOrderNotify.php</notify_url>
<trade_type>JSAPI</trade_type>
<openid>100003</openid>
</xml>
再輸入正確的 商戶密鑰,點擊按鈕即可計算出結果,與自己計算的結果進行比對。
- 本例中 Sign 方法接收兩個參數,$arrayobj 為數組類型,保存了所有參數名和參數值組成的字符串數組,方法輸出計算后的簽名結果。ToUrlParams 方法將參數數組拼接為URL格式字符串。
/**
* 生成簽名
* @param 參與簽名的內容組成的數組
* @param 是否使用 HMAC-SHA256算法,否則使用MD5
* @return 簽名字符串
* @author 試試手氣
* @version test pass
*/
public static function Sign($arrayobj, $hmacsha256 = true)
{
//步驟一:字典排序
ksort($arrayobj);
//步驟二:在
$str = Util::ToUrlParams($arrayobj);
//步驟三:在$str后面加KEY
$mchKey = "asdxxxxxxxxxxxxxxN82"; // 商戶支付秘鑰,生產代碼中是不可能如此硬編碼到這里的
$str .= "&key=" . $mchKey;
//步驟四:MD5或HMAC-SHA256C加密
if ($hmacsha256) {
$str = hash_hmac("sha256", $str, $mchKey);
} else {
$str = md5($str);
}
//步驟五:所有字符轉大寫
$result = strtoupper($str);
return $result;
}
public static function ToUrlParams($arrayobj)
{
$buff = "";
foreach ($arrayobj as $k => $v) {
if ($k != "sign" && $v != "" && !is_array($v)) {
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
將簽名結果作為最后的參數一起創建XML格式的POST數據
除 sign 簽名外,其它的參數要么是現成的,要么是可以運行時獲取到的如 spbill_create_ip。計算完簽名,所有的參數就都准備好了,把它們加工成XML,參數順序沒有提到有限制。
function toXML($paramsobj)
{
$xml = "<xml>\n";
foreach ($paramsobj as $key => $value) {
$xml .= "<" . $key . ">" . $value . "</" . $key . ">\n";
}
$xml .= "</xml>";
return $xml;
}
出來是這個樣子的
<xml>
<appid>wxcxxxxx5bbf</appid>
<mch_id>15xxxxxxxxxxxx61</mch_id>
<device_info>WEB</device_info>
<nonce_str>6743482E92E61DE587B52D6C7DEBFE79</nonce_str>
<body>xx科技-測試</body>
<out_trade_no>TY-OUTTRADENO-TEST-1561440044</out_trade_no>
<total_fee>1</total_fee>
<spbill_create_ip>36.xx.xx.185</spbill_create_ip>
<notify_url>http://wxpay.txxxt.com/jsapi/unifiedOrderNotify.php</notify_url>
<trade_type>JSAPI</trade_type>
<openid>100003</openid>
<sign>42DDF6C558EC05E24AAD0Cxxxxxxxx1DC3A53C0053D4A6F42E31036132F74481</sign>
</xml>
向微信統一下單API提交XML
用POST方式向統一下單接口地址提交數據,本例中使用的是微信SDK中的 postXmlCurl 方法改造了一下,用的是 curl 訪問網絡
function postXmlCurl($config, $xml, $url, $useCert = false, $second = 30)
{
$ch = curl_init();
//$curlVersion = curl_version();
//$ua = "WXPaySDK/".self::$VERSION." (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
// .$config->GetMerchantId();
//設置超時
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
// $proxyHost = "0.0.0.0";
// $proxyPort = 0;
// $config->GetProxy($proxyHost, $proxyPort);
// //如果有配置代理這里就設置代理
// 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);//嚴格校驗
//curl_setopt($ch,CURLOPT_USERAGENT, $ua);
//設置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求結果為字符串且輸出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if ($useCert == true) {
//設置證書
//使用證書:cert 與 key 分別屬於兩個.pem文件
//證書文件請放入服務器的非web目錄下
$sslCertPath = "";
$sslKeyPath = "";
$config->GetSSLCertPath($sslCertPath, $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);
}
//試試手氣新增,增加之后 curl 不報 60# 錯誤,可以請求到微信的響應
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //不驗證 SSL 證書
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//不驗證 SSL 證書域名
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//運行curl
$data = curl_exec($ch);
//返回結果
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
return "curl error, error code ".$error;
//throw new WxPayException("curl出錯,錯誤碼:$error");
}
}
沒有刪掉代碼,不用的注釋掉了,並增加了關於 CURLOPT_SSL_VERIFYPEER 和 CURLOPT_SSL_VERIFYHOST 的明確設置,在增加這兩句之前curl本身會報60錯誤。
返回值
按微信文檔,訪問(非業務)成功將返回(至少)包含 return_code 和 return_msg 兩個字段的XML文檔,對照統一下單接口文檔內的返回結果即可判斷請求是否成功,如下例
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxccxxxxbbf]]></appid>
<mch_id><![CDATA[15xxx61]]></mch_id>
<device_info><![CDATA[WEB]]></device_info>
<nonce_str><![CDATA[SMIpZtHhPPyTezlY]]></nonce_str>
<sign><![CDATA[7215B1BAB1C08A4887AAB0ECB2A6044729FE8A8BC6E70591660A6BCC11D7DC9A]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx251xxxxxxxxxxxxxxxxxxxxxxxxxxxxx078700]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
很明顯是訪問也成功了,業務也成功了,也就是接口業務成功生成了 預支付交易會話 wx251xxxxxxxxxxxxxxxxxxxxxxxxxxxxx078700 ,后面就該使用該會話標識調起H5支付了。