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