公众号支付的接口文档: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);