公衆号支付的接口文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
先寫好必要的方法:
const API_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
const API_ORDER_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
const API_CLOSE_ORDER = "https://api.mch.weixin.qq.com/pay/closeorder";
//簽名算法
public static function getSign($param, $appKey){
$param = array_filter($param);
ksort($param);
$stringA = "";
foreach ($param as $key=>$value){
if (!empty($value)){//值為空則不參與簽名
$stringA .= $key."=".$value."&";
}
}
$stringSignTemp = $stringA. "key=" .$appKey;
return strtoupper(md5($stringSignTemp));
}
//生成随機數
public static function getNonceStr(){
$nonce_str = '';
$max = mt_rand(16,32);
$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for ( $i = 0; $i < $max; $i++ ) {
$nonce_str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $nonce_str;
}
//數組轉XML
public static function arrayToXml($arr)
{
$xml = "<xml>";
foreach ($arr as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
//XML轉數組
public static function xmlToArray($xml)
{
//禁止引用外部xml實體
libxml_disable_entity_loader(true);
$values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $values;
}
public static function curlPost($url, $postData) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // https請求 不驗證證書和hosts
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
調用統一下單接口:
public function prePay($body,$userIp,$orderNo, $price, $userOpenId){
$postData = [
"appid" => self::APPID,
"mch_id" => self::MCHID,
"nonce_str" => self::getNonceStr(),
"body" => $body,
"out_trade_no" => $orderNo,
"total_fee" => (int)($price*100),//微信以分為機關
"spbill_create_ip" => $userIp,
"notify_url" => self::notify_url,
"trade_type" => "JSAPI",
"openid" => $userOpenId,
];
$postData["sign"] = self::getSign($postData, self::APPKEY);
$postData = Func::arrayToXml($postData);
$response = Func::xmlToArray(Func::curlPost(self::API_UNIFIED_ORDER, $postData));
if (isset($response["return_code"]) && $response["return_code"] == "SUCCESS"){
if ($response["result_code"] == "SUCCESS"){
$package = "prepay_id=".$response["prepay_id"];
$res = [
"appId" => self::APPID,
"timeStamp" => (string)time(),
"nonceStr" => self::getNonceStr(),
"package" => $package,
"signType" => "MD5"
];
$res["paySign"] = self::getSign($res, self::APPKEY);
return $res;//調起支付所需參數
}
}
return [];
}
public function endOrder($param){
if (isset($param["return_code"]) && $param["return_code"] == "SUCCESS" && $param["result_code"] == "SUCCESS"){
$resSign = $param["sign"];
unset($param["sign"]);
$orderInfo = "訂單資訊";
if (empty($orderInfo) || $param["total_fee"] != $orderInfo["price"]*100){
return false;
}
$sign = self::getSign($param, self::APPKEY);
if ($sign != $resSign){
return false;//簽名失敗
}
if ($param["appid"] != self::APPID){
return false;
}
if ($param["mch_id"] != self::MCHID){
return false;
}
if (1 == $orderInfo["pay_status"]){
return true;
}else{
//TODO 處理訂單
return true;
}
}
return false;
}
public function queryOrder($orderNO)
{
$nonce_str = self::getNonceStr();
$params = [
"appid" => self::APPID,
"mch_id" => self::MCHID,
"out_trade_no" => $orderNO,
"nonce_str" => $nonce_str
];
$params["sign"] = self::getSign($params, self::APPKEY);
$params = Func::arrayToXml($params);
$response = Func::xmlToArray(Func::curlPost(self::API_ORDER_QUERY, $params));
if (isset($response["return_code"]) && $response["return_code"] == "SUCCESS") {
if ($response["result_code"] == "SUCCESS" && $response["trade_state"] == "SUCCESS"){
return true;
}
}
return false;
}
public function closeOrder($orderNo){
$nonce_str = self::getNonceStr();
$params = [
"appid" => self::APPID,
"mch_id" => self::MCHID,
"out_trade_no" => $orderNO,
"nonce_str" => $nonce_str
];
$params["sign"] = self::getSign($params, self::APPKEY);
$params = Func::arrayToXml($params);
$response = Func::xmlToArray(Func::curlPost(self::API_CLOSE_ORDER, $params));
if (isset($response["return_code"]) && $response["return_code"] == "SUCCESS") {
$res_sign = $response["sign"];//傳回的簽名
unset($response["sign"]);
$gen_sign = self::getSign($response, self::APPKEY);
if ($res_sign != $gen_sign){
return false;
}
if ($response["appid"] != self::APPID || $response["mch_id"] != self::MCH_ID ){
return false;
}
if ($response["result_code"] == "SUCCESS"){
return true;
}
}
return false;
}
商戶訂單支付失敗需要生成新單号重新發起支付,要對原訂單号調用關單,避免重複支付;系統下單後,使用者支付逾時,系統退出不再受理,避免使用者繼續,請調用關單接口。
注意:訂單生成後不能馬上調用關單接口,最短調用時間間隔為5分鐘。調用關單或撤銷接口API之前,需确認支付狀态。
接收異步通知時要注意:
$param = file_get_contents("php://input");
$param = Func::xmlToArray($param);